Wstęp

Celem projektu była analiza zbioru danych dotyczącego materiałów wykorzystywanych w bateriach, z wykorzystaniem języka R. Dane te pochodzą z bazy Materials Project – inicjatywy Departamentu Energii USA, która dostarcza otwarte zasoby do badania właściwości i zastosowań materiałów.

Zbiór ten zawiera szczegółowe informacje o składzie chemicznym i parametrach wydajnościowych materiałów stosowanych w bateriach. Analiza pozwala lepiej zrozumieć ich właściwości, co wspiera rozwój innowacyjnych technologii magazynowania energii. Raport przedstawia wyniki przeprowadzonych badań oraz kluczowe wnioski.

Raport przedstawia kompleksową analizę zbioru danych dotyczącego właściwości materiałów do baterii oraz tworzenie modeli predykcyjnych. W pierwszej części dokonano szczegółowej analizy rozkładów poszczególnych atrybutów, co pozwoliło na zrozumienie ich charakterystyki i zidentyfikowanie potencjalnych zależności. Następnie skonstruowano modele predykcyjne dla zmiennych, takich jak energia grawimetryczna oraz średnie napięcie.

Raport zawiera także wizualizacje, które ułatwiają interpretację wyników, oraz metryki oceny modeli, takie jak R-squared, RMSE i MAE, co umożliwia obiektywną ocenę ich skuteczności.

W ramach analizy odkryto, że najczęściej wykorzystywanym jonem głównym jest lit. Zwizualizowano rozkłady atrybutów, pozwalajacę na kompleksową ocenę ich właściwości. Pozwoliło to na odkrycie iż nie ma w zbiorze znacznie dominujących wzorów chemicznych materiału baterii, wykorzystywanych jest wiele unikalnych wzorów. Podsumowano najważniejsze spostrzeżenia charakterystyk materiałów dla różnych grup jonu głównego.

Na podstawie macierzy korelacji wyznaczono atrybuty o największej zależności. Są to energia grawimetryczna i wolumetryczna oraz pojemność grawimetryczna i wolumetryczna. Skonstruowano modele predykcyjne wykorzystując metodę regresji liniowej i random forest. Wstępne dane zostały poddane preprocessingowi opartemu o kilka etapów takich jak usuwanie wartości odstających, normalizacja. Wybrane metryki pozwoliły na ocenę modeli, które wykazały się wysoką jakością predykcji.

Wykorzystane biblioteki

library(skimr)
library(corrplot)
library(GGally)
library(dplyr)
library(plotly)
library("Hmisc")
library(mlbench)
library(caret)
library(rlang)
library(knitr)
library(DT)
library(dplyr)
library(ggplot2)
library(gridExtra)
library(here)
library(tidyverse)
library(scales)
library(patchwork)
library(Metrics)
library(broom)
library(randomForest)

Jak wyglądają dane?

urlfile="https://raw.githubusercontent.com/KlaudiaK/Battery-materials-analysis/refs/heads/master/mp_batteries.csv"
data <- read.csv(url(urlfile))

data$Steps <- as.factor(data$Steps)
data$Working.Ion <- as.factor(data$Working.Ion)
data$Formula.Charge <- as.factor(data$Formula.Charge)
data$Formula.Discharge <- as.factor(data$Formula.Discharge)

W celu zilustrowania zawartości zbioru danych, wyświetlono kilka pierwszych wierszy zbioru, aby zaprezentować strukturę tabeli, nazwy zmiennych oraz przykłady wartości, które one przyjmują.

Atrybuty

W poniższej tabeli przedstawiono znajdujące się w zbiorze atrybuty oraz ich definicje.

Atrybut Opis
Battery ID Identyfikator baterii.
Battery Formula Wzór chemiczny materiału baterii.
Working Ion Główny jon, który odpowiada za transport ładunku w baterii.
Formula Charge Wzór chemiczny materiału baterii w stanie naładowanym.
Formula Discharge Wzór chemiczny materiału baterii w stanie rozładowanym.
Max Delta Volume Zmiana objętości w % dla danego kroku napięcia za pomocą wzoru : max(charge, discharge)/min(charge, discharge) -1.
Average Voltage Średnie napięcie dla poszczególnego kroku napięcia.
Gravimetric Capacity Pojemność grawimetryczna, czyli ilość energii na jednostkę masy (mAh/g).
Volumetric Capacity Pojemność wolumetryczna, czyli ilość energii na jednostkę objętości (mAh/cm³).
Gravimetric Energy Gęstość energii w odniesieniu do masy baterii (Wh/kg).
Volumetric Energy Gęstość energii w odniesieniu do objętości baterii (Wh/L).
Atomic Fraction Charge Udział atomowy składników w stanie naładowanym.
Atomic Fraction Discharge Udział atomowy składników w stanie rozładowanym.
Stability Charge Wskaźnik stabilności materiału w stanie naładowanym.
Stability Discharge Wskaźnik stabilności materiału w stanie rozładowanym.
Steps Liczba odrębnych kroków napięcia od pełnego naładowania do rozładowana, oparta na stabilnych stanach pośrednich.
Max Voltage Step Maksymalna bezwzględna różnica między sąsiednimi krokami napięcia.

Charakterystyka zbioru danych

Zbiór danych zawiera 17 atrybutów i 4351 rekordów.

Suma brakujących wartości w zbiorze: 0.

kable(colSums(is.na(data)), col.names = c("Liczba brakujących wartości"), caption = "Liczba brakujących wartości w kolumnach")
Liczba brakujących wartości w kolumnach
Liczba brakujących wartości
Battery.ID 0
Battery.Formula 0
Working.Ion 0
Formula.Charge 0
Formula.Discharge 0
Max.Delta.Volume 0
Average.Voltage 0
Gravimetric.Capacity 0
Volumetric.Capacity 0
Gravimetric.Energy 0
Volumetric.Energy 0
Atomic.Fraction.Charge 0
Atomic.Fraction.Discharge 0
Stability.Charge 0
Stability.Discharge 0
Steps 0
Max.Voltage.Step 0

Suma duplikatów: 0.

Zbiór danych nie zawiera brakujących wartości ani duplikatów, więc dane nie wymagają czyszczenia.

skim_summary <- skim(data)

num_character <- skim_summary %>% filter(skim_type == "character") %>% nrow()
num_numeric <- skim_summary %>% filter(skim_type == "numeric") %>% nrow()
num_logical <- skim_summary %>% filter(skim_type == "logical") %>% nrow()

Zbiór zawiera:
- kolumny znakowe: 2,
- kolumny numeryczne: 11,
- kolumny logiczne: 0

skim_summary
── Data Summary ────────────────────────
                           Values
Name                       data  
Number of rows             4351  
Number of columns          17    
_______________________          
Column type frequency:           
  character                2     
  factor                   4     
  numeric                  11    
________________________         
Group variables            None  

Analiza zbioru danych

W tej części zostanie przeprowadzona analiza wartości atrybutów w zbiorze danych. Celem tej analizy jest zrozumienie rozkładu, zmienności oraz kluczowych cech poszczególnych atrybutów, co pozwoli na lepszą interpretację danych. Analiza obejmie różne metody wizualizacji, takie jak histogramy, wykresy gęstości oraz wykresy pudełkowe, które umożliwią szybkie wychwycenie istotnych trendów, wartości odstających oraz charakterystyki rozkładu danych.

Rozkłady atrybutów

Atrybuty numeryczne

Sekcja obejmuje wizualizację rozkładów wartości dla atrybutów numerycznych. W górnej części znajduje się histogram, który ilustruje częstość występowania różnych wartości atrybutu przyporządkowanych do określonej liczby przedziałów. Pomarańczowy kolor reprezentuje linię gęstości rozkładu (tzw. density plot). Wykres gęstości jest używany do wizualizacji kształtu rozkładu danych, pozwalając na lepsze zrozumienie jego formy w porównaniu do histogramu. Na wykresie znajduje się również czerwona linia, oznaczająca średnią wartość atrybutu, oraz zółta oznaczająca medianę, co pozwala na szybką ocenę jego centralnego położenia.
Poniżej znajduje się wykres pudełkowy (tzw. boxplot), który wizualizuje rozproszenie wartości i pozwala na identyfikację wartości odstających.


for (attr_name in numerical_attrs) {
  if (is.numeric(data[[attr_name]])) {
    hist_plt <- data %>% 
      ggplot(mapping = aes_string(x = attr_name)) + 
      geom_histogram(aes(y = ..density..), bins = 100, fill = "midnightblue", alpha = 0.7) +
      geom_density(color = "darkorange", size = 1) +
      # Add lines for mean and median
      geom_vline(aes(xintercept = mean(get(attr_name), na.rm = TRUE), color = 'Mean'), linetype = "dashed", size = 1.3) +
      geom_vline(aes(xintercept = median(get(attr_name), na.rm = TRUE), color = 'Median'), linetype = "dashed", size = 1.3) +
      xlab(attr_name) +
      ylab("Frequency") +
      scale_color_manual(name = "", values = c(Mean = "red", Median = "yellow")) +
      theme(legend.position = c(0.9, 0.9), legend.background = element_blank())
    
    box_plt <- data %>% 
      ggplot(aes_string(x = attr_name, y = 1)) +
      geom_boxplot(fill = "#E69F00", color = "gray23", alpha = 0.7) +
      xlab(attr_name) +
      ylab("")

    combined_plot <- (hist_plt / box_plt) +
      plot_annotation(title = paste("Distribution of", attr_name),
                      theme = theme(
                        plot.title = element_text(hjust = 0.5)))
    
    print(combined_plot)
    grid::grid.lines(x = c(0, 1), y = c(0, 0), gp = grid::gpar(col = "black", lty = 2))
  } else {
    message(paste(attr_name, "is not numeric and will be skipped."))
  }
}

NA
NA

Analizując rozkłady zmiennych, można zauważyć kilka kluczowych tendencji. Większość zmiennych charakteryzuje się silnie skośnymi rozkładami z dużą koncentracją wartości blisko zera oraz ogonami wydłużającymi się w kierunku wartości maksymalnych. Takie rozkłady sugerują istnienie znaczącej liczby obserwacji z niskimi wartościami oraz nieliczne przypadki ekstremalnych wyników.

Na histogramach można zauważyć wyraźne piki dla wartości centralnych w niektórych przypadkach, a linie gęstości pomagają uwidocznić kształt tych rozkładów. Średnia (czerwona linia przerywana) i mediana (żółta linia przerywana) często znajdują się bardzo blisko siebie, co wskazuje na umiarkowaną symetrię w części zmiennych. Niemniej jednak, w przypadku niektórych zmiennych, takich jak „Gravimetric.Capacity” czy „Volumetric.Capacity”, różnica między średnią a medianą sugeruje wpływ wartości skrajnych na rozkład.

Wykresy pudełkowe uzupełniają analizę, uwidaczniając obecność licznych wartości odstających w większości zmiennych. Obserwacje odstające są szczególnie widoczne dla zmiennych takich jak „Gravimetric.Energy” czy „Volumetric.Energy”.

Dla poszczególnych atrybutów można wyciągnąć wnioski, że większość wartości koncentruje się w zakresie:

  • Max.Delta.Volume - koncentracja wartości blisko 0, nieliczne wartości odstające i extremalne
  • Average.Voltage - 2 - 4, rozkład jest asymetryczny ze względu na dużą liczbę wartości odstających
  • Gravimetric.Capacity - 88 - 200, liczne wartości odstające, rozkład z długin ogonem po prawej stronie
  • Volumetric Capacity - 310 - 720, rozkład z dwoma dominantami
  • Gravimetric Energy - 210 - 615, rozkład z długin ogonem po prawej stronie
  • Volumetric Energy - 820 - 2250, rozkład z długin ogonem po prawej stronie
  • Atomic Fraction Charge - 0 - 0.05, średnia mocno przesunięta na prawo od mediany, co może być spowodowane licznymi przypadkami odstającymi
  • Atomic Fraction Discharge - 0.086 - 0.2
  • Stability.Charge - koncentracja w zakresie 0.03 - 0.13, liczne wartości odstające, rozkład asymetryczny z długim ogonem po prawej stronie
  • Stability.Discharge - koncentracja w zakresie 0.02 - 0.09, liczne wartości odstające, rozkład asymetryczny z długim ogonem po prawej stronie
  • Max Voltage Step - silna koncentracja wartości blisko 0

Główne jony

Tabela przedstawia mediany wartości atryburów numerycznych, dla poszczególnych grup głównego jonu transportującego ładunek (Working Ion).


ion_mean_sum <-  data %>%
    select(Working.Ion, Max.Delta.Volume:Stability.Discharge) %>%
    group_by(Working.Ion) %>%
    summarise(across(everything(), median))

prettyTable(ion_mean_sum)

data %>%
  group_by(Working.Ion) %>%
  summarise(total_ion = n()) %>%
  ggplot() +
   labs(title = paste("Rozkład głównych jonów odpowiadających za transport ładunku w baterii"),
           x = "Working.Ion",
           y = "Count") +
      geom_bar(aes(x = reorder(Working.Ion, total_ion), y = total_ion, fill = Working.Ion), stat = "identity") +
      theme_minimal() 

Na przedstawionym wykresie zaprezentowano rozkład głównych jonów używanych do transportu ładunku w bateriach. Wyraźnie dominuje lit (Li), który występuje znacznie częściej niż inne jony tj. prawie 2500 razy. Sugeruje to powszechne zastosowanie technologii opartych na litowo-jonowych rozwiązaniach, co jest zgodne z ich szerokim wykorzystaniem w przemyśle elektroniki i magazynowania energii. Szczególną zaletą baterii litowo-jonowych jest wysoką gęstość energii. To znaczy, że mogą magazynować dużą ilość energii przy niewielkich rozmiarach i niskiej wadze, co czyni je doskonałym rozwiązaniem dla przenośnych urządzeń, takich jak laptopy i tablety („Litowo-jonowe a litowo-polimerowe: Szczegółowe porównanie” 2024). W zestawieniu pierwiastków lit plasuje się na drugim miejscu pod względem grawimetrcznej gęstości energii, zaraz za wapniem.
Pozostałe jony, takie jak wapń (Ca), magnez (Mg), i cynk (Zn), również znajdują zastosowanie, ale w znacznie mniejszym zakresie. Obecność jonów takich jak sód (Na) i potas (K) mogą wskazywać na badania nad alternatywami dla litu, jednak ich zastosowanie jest obecnie ograniczone.


Poniżej znajdują się interaktywne wykresy pudełkowe dla każdej zmiennej numerycznej w zbiorze danych z podziałem na główne jony. Wykresy te umożliwiają eksplorację rozkładu wartości, identyfikację potencjalnych wartości odstających oraz porównanie zmienności w każdej zmiennej dla poszególnych jonów.

for (attr_name in numerical_attrs) {
  p <- plot_ly(
    data, 
    y = ~get(attr_name),
    type = "box",
    boxpoints = "all",
    jitter = 0.3,
    pointpos = -1.8,
    hoverInfo = "text",
    color = ~Working.Ion
  ) %>%
    layout(
      title = paste("Boxplot of", attr_name),
      yaxis = list(title = attr_name)
    )

  print(p)
}
NA

Wnioski z analizy wykresów rozkładów wartości wybranych atrybutów z podziałem na główny jon:

  • Dla średniego napięcia lit charakteryzuje się najwęższym przedziałem najczęstszych wartości, ale jednocześnie jest jonem, dla którego można zidentyfikować najwięcej wartości odstających.
  • W przypadku pojemności grawimetrycznej jonami, które charakteryzują się największą liczbą silnie odstających obserwacji są magnez (Mg) i glin (Al).
  • Na tla wszystkich cech, cez wykazuje największe skupienie wartości, w porównaniu do innych jonów posiada najmniej wartości odstających.
  • Najwięcej wartości silnie odstających można przypisać litowi, magnezowi, i glinowi. Liczne wartości odstające w przypadku litu mogą być spowowodwane dominacją w wykorzystaniu nad innymi jonami.
  • Warto zauważyć, że lit, najczęściej wykorzystywany jako jon główny, w większości cech charakteryzuje się równomiernym rozkładem, co może być istotnym czynnikiem decydującym o jego dominacji.

Wzory chemiczne materiału baterii

formula_charge_summary <- data %>%
  group_by(Formula.Charge) %>%
  summarise(total = n()) %>%
  arrange(desc(total))

percentile_99 <- quantile(select(formula_charge_summary, total), probs = 0.99, na.rm = TRUE)
formula_charge_values_99_percentile <- formula_charge_summary %>%
  filter(total >= percentile_99)

kable(formula_charge_values_99_percentile)
Formula.Charge total
MnO2 49
TiO2 47
VO2 46
CrO2 45
CoO2 43
NiO2 41
FeO2 36
FePO4 26
WO2 25
CoPO4 24
MnP2O7 22
MnPO4 22
VF5 22
CoP2O7 20
FeP2O7 20
V2OF5 20
WO3 20
MoO2 19
V2O5 19
VPO5 18
CrP2O7 17
MnFeCo(PO4)3 17
VP2O7 17

formula_charge_values_99_percentile %>%
  ggplot() +
   labs(title = paste("Wzóry chemiczne materiału baterii w stanie naładowanym"),
           x = "Wzór w stanie naładowanym",
           y = "Liczba obserwacji") +
      geom_bar(aes(x = reorder(Formula.Charge, total), y = total, fill = Formula.Charge), stat = "identity",  width = 0.5) +
      theme(
        text = element_text(size = 12),
        axis.text.x = element_text(angle = 90, hjust = 1, size = 8)
      )

Wykres przedstawia liczbę wystąpień różnych wzorów chemicznych materiałów baterii w stanie naładowanym. W zbiorze jest 2096 różnych wzorów chemicznych materiałów baterii w stanie naładowanym. Najliczniejszymi są MnO2, TiO2, Vo2, CrO2, NiO2, FeO2.



formula_discharge_summary <- data %>%
  group_by(Formula.Discharge) %>%
  summarise(total = n()) %>%
  arrange(desc(total))

discharge_percentile_99 <- quantile(select(formula_discharge_summary, total), probs = 0.99, na.rm = TRUE)
formula_discharge_values_99_percentile <- formula_discharge_summary %>%
  filter(total >= discharge_percentile_99)

formula_discharge_values_99_percentile

formula_discharge_values_99_percentile %>%
  ggplot() +
   labs(title = paste("Wzóry chemiczne materiału baterii w stanie rozładowanym"),
           x = "Wzór w stanie rozładowanym",
           y = "Liczba oserwacji") +
      geom_bar(aes(x = reorder(Formula.Discharge, total), y = total, fill = Formula.Discharge), stat = "identity",  width = 0.5) +
      theme(
        text = element_text(size = 12),
        axis.text.x = element_text(angle = 90, hjust = 1, size = 8),
        plot.title = element_text(size = 30)
      )

Wykres przedstawia liczbę wystąpień różnych wzorów chemicznych materiałów baterii w stanie rozładowanym. W zbiorze jest 3173 różnych wzorów chemicznych materiałów baterii w stanie rozładowanym. Najczęściej występujące wzory to LiVOF11, Li2O5F5, LiFePO4, LiCoPO4. Znaczna większość najczęściej występujących wzorów zawiera cząsteczkę litu.

Analiza właściwości

Stabilność w stanie naładowanym i rozładowanym

Stability Charge

  • Określa stabilność materiału w baterii, gdy jest w pełni naładowany
  • Wyższa stabilność oznacza, że materiał będzie mniej podatny na uszkodzenia lub degradację podczas ładowania. Jest to istotne, aby zapewnić długotrwałe działanie baterii bez utraty jej właściwości.

Stability Discharge

  • Określa stabilność materiału w baterii, gdy jest w pełni rozładowany.
  • Stabilność w stanie rozładowanym jest kluczowa dla utrzymania efektywności baterii przez wiele cykli ładowania i rozładowania. Zapewnia ona, że materiał nie ulegnie degradacji, co mogłoby prowadzić do zmniejszenia pojemności i żywotności baterii.
summary_stability_charge_working_ion <- data %>%
  group_by(Working.Ion) %>%
  summarise(stability_charge_mean = mean(Stability.Charge),
            stability_charge_median = median(Stability.Charge),
            stability_charge_min = min(Stability.Charge),
            stability_charge_max = max(Stability.Charge),
            total = n()) %>%
  arrange(desc(stability_charge_median))

p_stability_charge_working_ion <- ggplot(summary_stability_charge_working_ion, aes(x = total, y = stability_charge_median, color = Working.Ion)) +
  geom_point(aes(
    text = paste(
      "Working Ion:", Working.Ion,
      "<br>Median :", round(stability_charge_median, 2),
      "<br>Mean :", round(stability_charge_mean, 2),
      "<br>Min :", round(stability_charge_min, 2),
      "<br>Max :", round(stability_charge_max, 2)
    )
  ),  size = 3) +
  labs(
    title = "Stabilność w stanie naładowanym a główny jon",
    x = "Liczba obserwacji",
    y = "Wskażnik stabilności w stanie naładowanym"
  ) +
  theme_minimal()

ggplotly(p_stability_charge_working_ion, tooltip = "text")

summary_stability_discharge_working_ion <- data %>%
  group_by(Working.Ion) %>%
  summarise(stability_discharge_mean = mean(Stability.Discharge),
            stability_discharge_median = median(Stability.Discharge),
            stability_discharge_min = min(Stability.Discharge),
            stability_discharge_max = max(Stability.Discharge),
            total = n()) %>%
  arrange(desc(stability_discharge_median))

p_stability_discharge_working_ion <- ggplot(summary_stability_discharge_working_ion, aes(x = total, y = stability_discharge_median, color = Working.Ion)) +
  geom_point(
    aes(
    text = paste(
      "Working Ion:", Working.Ion,
      "<br>Median :", round(stability_discharge_median, 2),
      "<br>Mean :", round(stability_discharge_mean, 2),
      "<br>Min :", round(stability_discharge_min, 2),
      "<br>Max :", round(stability_discharge_max, 2)
    )
  ), size = 3) +
  labs(
    title = "Stabilność w stanie rozładowanym a główny jon",
    x = "Liczba obserwacji",
    y = "Wskażnik stabilności w stanie rozładowanym"
  ) +
  theme_minimal()

ggplotly(p_stability_discharge_working_ion, tooltip = "text")

Stabilność w stanie naładowanym:

  • Wartości wskaźnika stabilności materiałów w większości mieszczą się poniżej 0,15
  • Jony wykazujące najwyższą stabilność w stanie naładowanym to Y, Al, Zn, Ca i Mg
  • Jony wykazujące najniższą stabilność w stanie naładowanym to

Stabilność w stanie rozładowanym:

  • Wartości wskaźnika stabilności materiałów w większości mieszczą się poniżej 0,10
  • Jony wykazujące najwyższą stabilność w stanie naładowanym to Y, Mg, Zn oraz Al

Szczególną uwagę przyciąga lit, który charakteryzuje się największą liczbą obserwacji i jednocześnie zajmuje pośrednie miejsce w zestawieniu pod względem wartości wskaźników stabilności.

Zmiana objętości dla danego kroku napięcia z podziałem na jony główne


summary_max_data_volume_working_ion <- data %>%
  group_by(Working.Ion) %>%
  summarise(max_data_volume_mean = mean(Max.Delta.Volume),
            max_data_volume_median = median(Max.Delta.Volume),
            max_data_volume_min = min(Max.Delta.Volume),
            max_data_volume_max = max(Max.Delta.Volume),
            total = n()) %>%
  arrange(max_data_volume_mean)

prettyTable(summary_max_data_volume_working_ion)

p <- ggplot(summary_max_data_volume_working_ion, aes(x = total, y = max_data_volume_median, color = Working.Ion)) +
  geom_point(aes(
    text = paste(
      "Working Ion:", Working.Ion,
      "<br>Median :", round(max_data_volume_median, 2),
      "<br>Mean :", round(max_data_volume_mean, 2),
      "<br>Min :", round(max_data_volume_min, 2),
      "<br>Max :", round(max_data_volume_max, 2)
    )
  ), size = 3, alpha = 0.7) +
  labs(
    title = "Mediana zmiany objętości w % dla danego kroku napięcia dla jonów głównych",
    x = "Liczba obserwacji",
    y = "Mediana maksymalnej zmiany objętości"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")

ggplotly(p, tooltip = "text")

Najniższą zmianą napięcia charakteryzuje się lit (Li), a najwyższą itr (Y).

Pojemność i gęstość grawimetryczna

Gęstość Grawimetryczna

  • Wskazuje ile energii dostępnej jest w baterii w odniesieniu do jej masy (Wh/kg).
  • Gęstość grawimetryczna informuje, jak efektywnie bateria magazynuje energię w stosunku do swojej wagi. Jest kluczowa dla aplikacji, gdzie ważna jest zarówno pojemność energetyczna, jak i lekkość, np. w samochodach elektrycznych czy dronach.

summary_gravimetric_energy_working_ion <- data %>%
  group_by(Working.Ion) %>%
  summarise(gravimetric_energy_mean = mean(Gravimetric.Energy),
            gravimetric_energy_median = median(Gravimetric.Energy),,
            gravimetric_energy_min = min(Gravimetric.Energy),
            gravimetric_energy_max = max(Gravimetric.Energy),
            total = n()) %>%
  arrange(desc(gravimetric_energy_median))

prettyTable(summary_gravimetric_energy_working_ion)

p <- ggplot(summary_gravimetric_energy_working_ion, aes(x = total, y = gravimetric_energy_median, color = Working.Ion)) +
  geom_point(aes(
    text = paste(
      "Working Ion:", Working.Ion,
      "<br>Median :", round(gravimetric_energy_median, 2),
      "<br>Mean :", round(gravimetric_energy_mean, 2),
      "<br>Min :", round(gravimetric_energy_min, 2),
      "<br>Max :", round(gravimetric_energy_max, 2)
    )
  ), size = 3, alpha = 0.7) +
  labs(
    title = "Mediana energii grawimetrycznej dla jonów głównych",
    x = "Liczba obserwacji",
    y = "Mediana energii grawimetrycznej"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")

ggplotly(p, tooltip = "text")
NA
NA
  • Największą gęstością grawimetryczną charakteryzują się materiały Ca, Li, Y, Na, Mg
  • Najmniejszą gęstością grawimetryczną charakteryzują się materiały Cs, Zn, Rb

Pojemność Grawimetryczna

  • Wskazuje ile energii elektrycznej bateria może przechować w przeliczeniu na jednostkę masy (mAh/g).
  • Im wyższa pojemność grawimetryczna, tym więcej energii bateria może przechowywać przy tej samej masie. Jest to istotne dla urządzeń przenośnych, gdzie zależy nam na maksymalizacji energii przy niewielkiej masie.
summary_gravimetric_capacity_working_ion <- data %>%
  group_by(Working.Ion) %>%
  summarise(gravimetric_capacity_mean = mean(Gravimetric.Capacity),
            gravimetric_capacity_median = median(Gravimetric.Capacity),
            gravimetric_capacity_min = min(Gravimetric.Capacity),
            gravimetric_capacity_max = max(Gravimetric.Capacity),
            total = n()) %>%
  arrange(desc(gravimetric_capacity_median))

prettyTable(summary_gravimetric_capacity_working_ion)

p <- ggplot(summary_gravimetric_capacity_working_ion, aes(x = total, y = gravimetric_capacity_median, color = Working.Ion)) +
  geom_point(aes(
    text = paste(
      "Working Ion:", Working.Ion,
      "<br>Median :", round(gravimetric_capacity_median, 2),
      "<br>Mean :", round(gravimetric_capacity_mean, 2),
      "<br>Min :", round(gravimetric_capacity_min, 2),
      "<br>Max :", round(gravimetric_capacity_max, 2)
    )
  ), size = 3, alpha = 0.7) +
  labs(
    title = "Mediana pojemności grawimetrycznej dla głównych jonów",
    x = "Liczba obserwacji",
    y = "Mediana pojemności grawimetrycznej"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")

ggplotly(p, tooltip = "text")
NA
  • Największą pojemnością grawimetryczną charakteryzują się materiały Al, Y, Mg, Ca, Zn
  • Najmniejszą pojemnością grawimetryczną charakteryzują się materiały Cs, Rb

Zarówno rubid (Rb) jak i cez (Cs), które wypadają najgorzej pod względem energii i gestości grawimetrycznej są najrzadziej wykorzystywanymi jonami głównymi.


Poniższy wykres przedstawia zależność między liczbą obserwacji dla najczęściej występujących Wzorów chemicznych materiałów baterii w stanie naładowanym a medianą stabilności w stanie naładowanym. Dla każdego wzoru obliczono miary statyczne, które wyświetlają się po najechaniu na punkt.


formula_charge_summary <- data %>%
  group_by(Formula.Charge) %>%
  summarise(total = n()) %>%
  arrange(desc(total))

percentile_99 <- quantile(select(formula_charge_summary, total), probs = 0.99, na.rm = TRUE)
formula_charge_values_99_percentile <- formula_charge_summary %>%
  filter(total >= percentile_99)

summary_data <- data %>%
  filter(Formula.Charge %in% formula_charge_values_99_percentile$Formula.Charge) %>%
  group_by(Formula.Charge) %>%
  summarise(total = n(),
    stability_charge_mean = mean(Stability.Charge),
            stability_charge_median = median(Stability.Charge),
            stability_charge_min = min(Stability.Charge),
            stability_charge_max = max(Stability.Charge)) %>%
  arrange(desc(total))

p <- ggplot(summary_data, aes(x = total, y = stability_charge_median)) +
  geom_point(aes(
    text = paste(
      "Formula charge:", Formula.Charge,
      "<br>Stability charge median:", round(stability_charge_median, 4),
      "<br>Stability charge mean:", round(stability_charge_mean, 4),
      "<br>Min Stability:", round(stability_charge_min, 4),
      "<br>Max Stability:", round(stability_charge_max, 4)
    ),
    size = total, color = stability_charge_median
  ), alpha = 0.7) +
  scale_color_gradient(low = "blue", high = "red") +
  scale_size_continuous(range = c(2, 10)) +
  labs(
    title = "Mediana stabilności w stanie naładowanym <br>dla najczęstszych wzorów chemicznych materiałów baterii",
    x = "Liczba obserwacji",
    y = "Mediana stabilności w stanie naładowanym"
  ) +
  theme_minimal() +
  theme(
    legend.position = "bottom",
    plot.title = element_text(hjust = 0.5)
  )

ggplotly(p, tooltip = "text")
NA

Korelacja zmiennych

Poniższa macierz korelacji ilustruje współczynniki korelacji Pearsona dla wybranych atrybutów. Kolory kafelków reprezentują siłę oraz kierunek korelacji. Odcienie niebieskiego wskazują na dodatnią korelację, a odcienie czerwonego na ujemną.

number_attr_data <- select(data, (numerical_attrs))

cor_matrix <- cor(number_attr_data, method = "pearson")

corrplot.mixed(cor_matrix,
  tl.pos = 'lt',
  order = 'AOE',
  lower = 'shade',
  upper = 'number'
)

Najwyższy wspolczynnik korelacji wystepuje pomiedzy parami atrybutów:
- Gravimetric Energy i Volumetric Energy - 0.93
- Gravimetric Capcity i Volumetric Capacity - 0.86
- Stability Charge i Stability Discharge - 0.80
- Gravimetric Capacity i Atomic Fraction Discharge - 0.68
- Average Voltage i Gravimetric Energy - 0.67

Korelacja pomiędzy energią grawimetryczną i wolumetryczną

gravimetric_volumetric_energy_correlation <- cor(data$Gravimetric.Energy, data$Volumetric.Energy, use = "complete.obs")

ggplot(data, aes(x = Gravimetric.Energy, y = Volumetric.Energy)) +
  geom_point(color = "darkgreen", size = 2, alpha = 0.7) +
  geom_smooth(method = "lm", color = "blue", se = FALSE, linetype = "dashed") +
  geom_smooth(method = "loess", color = "red", se = TRUE, fill = "gray80") +
  labs(title = "Korelacja między grawimetryczną i wolumetryczną gęstością energii",
       subtitle = paste("Współczynnik korelacji =", round(gravimetric_volumetric_energy_correlation, 2)),
       x = "Grawimetryczna gęstość energii (Wh/kg)",
       y = "Wolumetryczna gęstość energii (Wh/L)") +
  theme_minimal()

Wykres przedstawia zależność między wolumetryczną (Wh/L, energia na jednostkę objętości) i grawimetryczną (Wh/kg, energia na jednostkę masy) gęstością energii, gdzie widoczna jest silna korelacja dodatnia między tymi parametrami. Gęstość energii jest kluczowym wskaźnikiem wydajności baterii - im wyższa wartość, tym więcej energii może być zmagazynowane w danej objętości lub masie baterii, co jest szczególnie istotne w zastosowaniach mobilnych, takich jak pojazdy elektryczne czy urządzenia przenośne. Większość badanych materiałów skupia się w zakresie do 2000 Wh/kg i 7500 Wh/L, choć występuje kilka obiecujących wyjątków o wyższych parametrach, które mogą stanowić potencjalne kierunki rozwoju nowych, wydajniejszych baterii.

Korelacja pomiędzy pojemnością grawimetryczną i wolumetryczną

gravimetric_volumetric_capacity_correlation <- cor(data$Gravimetric.Capacity, data$Volumetric.Capacity, use = "complete.obs")

ggplot(data, aes(x = Gravimetric.Capacity, y = Volumetric.Capacity)) +
  geom_point(color = "darkgreen", size = 2, alpha = 0.7) +
  geom_smooth(method = "lm", color = "blue", se = FALSE) +
  geom_smooth(method = "loess", color = "red", se = TRUE, fill = "gray80") +
  labs(title = "Korelacja między grawimetryczną i wolumetryczną pojemnością ",
       subtitle = paste("Correlation =", round(gravimetric_volumetric_capacity_correlation, 2)),
       x = "Pojemność grawimetryczna (mAh/g)",
       y = "Pojemność wolumetryczna (mAh/cm³)") +
  theme_minimal()

Wykres przedstawia zależność między wolumetryczną (mAh/cm³, ilość ładunku na jednostkę objętości) i grawimetryczną (mAh/g, ilość ładunku na jednostkę masy) pojemnością. Pojemność grawimetryczna określa ile energii można zmagazynować w danej masie materiału, a wolumetryczna - ile w danej objętości, co ma kluczowe znaczenie przy projektowaniu baterii o różnym przeznaczeniu. Współczynnik korelacji 0.86 wskazuje na silną zależność między tymi parametrami, choć nie tak silną jak w przypadku gęstości energii. Na przykład, materiał o wysokiej pojemności grawimetrycznej może być lekki, ale zajmować dużo miejsca, podczas gdy materiał o wysokiej pojemności wolumetrycznej może być cięższy, ale bardziej kompaktowy („Co warto wiedzieć o ogniwach litowo‑jonowych?” 2024).

Korelacja pomiędzy stabilnością materiału w stanie naładowanym i rozładowanym

stability_charge_discharge_coorelation <- cor(data$Stability.Charge, data$Stability.Discharge, use = "complete.obs")

ggplot(data, aes(x = Stability.Charge, y = Stability.Discharge)) +
  geom_point(color = "darkgreen", size = 2, alpha = 0.7) +
  geom_smooth(method = "lm", color = "blue", se = FALSE, linetype = "dashed") +
  geom_smooth(method = "loess", color = "red", se = TRUE, fill = "gray80") +
  labs(title = "Korelacja materiału w stanie naładowanym i rozładowanym",
       subtitle = paste("Correlation =", round(stability_charge_discharge_coorelation, 2)),
       x = "Stabilność w stanie naładowanym",
       y = "Stabilność w stanie rozładowanym") +
  theme_minimal()

Wykres przedstawia zależność między stabilnością materiału w stanie naładowanym (Stability Charge) i w stanie rozładowanym (Stability Discharge), ze współczynnikiem korelacji 0.8 wskazującym na silną dodatnią zależność. Stabilność materiału jest kluczowym parametrem określającym, jak dobrze materiał zachowuje swoją strukturę i właściwości podczas cykli ładowania i rozładowania - im niższa wartość, tym materiał jest bardziej stabilny i bezpieczny w użytkowaniu. Większość badanych materiałów skupia się w zakresie niskich wartości (0-2) dla obu parametrów, co jest pożądane, natomiast punkty odstające o wyższych wartościach (powyżej 4) mogą wskazywać na materiały problematyczne, które mogą być mniej odpowiednie do zastosowań w bateriach ze względu na potencjalną niestabilność.

Korelacja pomiędzy pojemnością grawimetryczną i udział atomowym składników w stanie rozładowanym

gravimetric_capacity_atomic_fraction_discharge_coorelation <- cor(data$Gravimetric.Capacity, data$Atomic.Fraction.Discharge, use = "complete.obs")

ggplot(data, aes(x = Gravimetric.Capacity, y = Atomic.Fraction.Discharge)) +
  geom_point(aes(color = Atomic.Fraction.Discharge), size = 3, alpha = 0.7) +
  geom_smooth(method = "loess", color = "red", se = TRUE, fill = "gray80") +
  labs(title = "Korelacja między pojemnnością grawimetryczną a udział atomowym składników w stanie rozładowanym",
       subtitle = paste("Correlation =", round(gravimetric_capacity_atomic_fraction_discharge_coorelation, 2)),
       x = "Pojemność grawimetryczna (mAh/g)",
       y = "Udział atomowy składników w stanie rozładowanym.") +
  scale_color_gradient(low = "orange", high = "purple") +
  theme_minimal()

Wykres przedstawia zależność między pojemnością grawimetryczną (Gravimetric Capacity, mAh/g) i udziałem atomowym w stanie rozładowania (Atomic Fraction Discharge). Można zaobserwować umiarkowanie silną dodatnią zależność, co potwierdza współczynnik korelacji wynoszący 0.68. W miarę wzrostu pojemności grawimetrycznej, udział atomowy w stanie rozładowania zwiększa się, osiągając wartość maksymalną bliską 1.0.
Kolor punktów reprezentuje wartość Atomic Fraction Discharge, gdzie jaśniejsze kolory wskazują na niższe wartości, a ciemniejsze na wyższe. Dane wskazują, że większość obserwacji znajduje się w zakresie niskiej pojemności grawimetrycznej (<1000 mAh/g), a dla wartości powyżej 2000 mAh/g zależność staje się nieliniowa. Sugeruje to, że materiały o wyższej pojemności grawimetrycznej mają tendencję do osiągania wyższych udziałów atomowych w stanie rozładowania.

Korelacja pomiędzy średnim napięciem a energią grawimetryczną

filteredData <- data %>%filter(data$Steps == 3)
correlation <- cor(data$Average.Voltage, data$Gravimetric.Energy, use = "complete.obs")

ggplot(data, aes(x = Average.Voltage, y = Gravimetric.Energy)) +
  geom_point(color = "darkblue", size = 3, alpha = 0.7) +
  geom_smooth(method = "lm", color = "darkred", se = FALSE, linetype = "dashed") +
  labs(title = "Korelacja między średnim napięciem i energią grawimetryczną",
       subtitle = paste("Correlation =", round(correlation, 2)),
       x = "Średnie napięcie (V)",
       y = "Energia grawimetryczna (Wh/kg)") +
  theme_classic()


data_with_cor <- data %>%
  group_by(Steps) %>%
  summarise(
    correlation = cor(Average.Voltage, Gravimetric.Energy, use = "complete.obs"),
    n_cases = n() 
  )

p <- ggplot(data, aes(x = Average.Voltage, y = Gravimetric.Energy)) +
   geom_point(aes(text = paste("Step:", Steps, 
                              "<br>Voltage:", Average.Voltage, 
                              "<br>Energy:", Gravimetric.Energy, 
                              "<br>Ion:", Working.Ion)), 
             color = "darkblue", size = 3, alpha = 0.7) +
  geom_smooth(method = "lm", color = "darkred", se = FALSE, linetype = "dashed") +
  facet_wrap(~ Steps, scales = "fixed",   ncol = 2, 
             labeller = labeller(Steps = function(x) {
              row <- data_with_cor[data_with_cor$Steps == x, ]
              paste0("Step = ", x, " (Corr = ", round(row$correlation, 2), 
                      ", n = ", row$n_cases, ")")
             })) +
   labs(title = "Średnie napięcie a energia grawimetryczna przy uwzględnieniu wartości kroku",
       x = "Średnie napięcie (V)",
       y = "Energia grawimetryczna (Wh/kg)")  +
  theme_grey() +
 theme(
    plot.title = element_text(margin = ggplot2::margin(t = 10, b = 10), hjust = 0.5),
    axis.title.x = element_text(margin = ggplot2::margin(t = 10)),
    axis.title.y = element_text(margin = ggplot2::margin(r = 10)),
    plot.margin = ggplot2::margin(20, 20, 20, 20),
    panel.spacing = unit(2, "cm")
  )

ggplotly(p,  tooltip = "text")

Wykresy przedstawiają zależności między średnim napięciem (Average Voltage, V) a gęstością energii grawimetrycznej (Gravimetric Energy Density, Wh/kg) dla różnych wartości kroku napięcia. Najwięcej obserwacji jest dla kroku równego 1 i jest to najbardziej reprezentatywna próbka. W tym przypadku współczynnik korelacji jest na wysokim poziomie, bo wynosi 0.69. Dane wskazują, że wraz ze wzrostem średniego napięcia zwiększa się gęstość energii grawimetrycznej. Większość danych skupia się w zakresie niskich wartości napięcia (<10 V), co sugeruje, że materiały o wyższym napięciu są mniej liczne, ale mogą wykazywać większą efektywność energetyczną.

Predykcja cech i właściwości baterii

W tej sekcji zostaną przeanalizowane dwa modele predykcyjne dotyczące właściwości baterii. Pierwszy model, oparty na regresji liniowej, skupia się na przewidywaniu energii grawimetrycznej. Drugi model, wykorzystujący algorytm Random Forest, przewiduje średnie napięcie. W obu przypadkach celem jest ocena skuteczności zastosowanych metod oraz identyfikacja kluczowych czynników wpływających na wyniki.

Predykcja energii grawimetrycznej metodą regresji

Preprocessing danych

Preprocessing danych jest kluczowym etapem przygotowania zbioru treningowego, ponieważ wpływa bezpośrednio na jakość, precyzję i zdolność predykcyjną modelu, eliminując szumy, redundancje i potencjalne źródła błędów, co ostatecznie decyduje o skuteczności i wiarygodności całego modelu.

attributes_to_remove <- colnames(select(data, c(Battery.ID, Battery.Formula, Formula.Discharge)))

W pierwszym kroku usunięto wybrane kolumny takie jak Battery.ID, Battery.Formula, Formula.Discharge, które nie wnosiły bezpośrednich informacji numerycznych do modelowania.

battery_train_data <- select(data, -(attributes_to_remove))
cor_thereshold <- 0.8
cor_with_target <- cor(dataset_numerical_attrs, use = "pairwise.complete.obs")[, "Volumetric.Energy"]
high_cor_to_target <- names(which(cor_with_target > 0.8))
battery_train_data <- battery_train_data %>%
  select(-all_of(high_cor_to_target), Volumetric.Energy)

Następnie, obliczono korelację zmiennych numerycznych ze zmienną celu, zidentyfikowano i usunięto zmienne o wysokiej korelacji liniowej (przekraczającej wartość 0.8). Atrybuty, które zostały usunięte to Average.Voltage.


outlier_threshold <- 2

detect_outliers <- function(x) {
  Q1 <- quantile(x, 0.25, na.rm = TRUE)
  Q3 <- quantile(x, 0.75, na.rm = TRUE)
  IQR <- Q3 - Q1
  return(x < (Q1 - 1.5 * IQR) | x > (Q3 + 1.5 * IQR))
}

outlier_matrix <- as.data.frame(lapply(battery_train_data, function(column) {
  if (is.numeric(column)) {
    return(detect_outliers(column))
  } else {
    return(rep(FALSE, length(column)))
  }
}))

outlier_count <- rowSums(outlier_matrix)
nrow(battery_train_data[outlier_count >= outlier_threshold,])
[1] 716
data_cleaned <- battery_train_data[outlier_count < outlier_threshold, ]
skim(data_cleaned)
── Data Summary ────────────────────────
                           Values      
Name                       data_cleaned
Number of rows             3635        
Number of columns          13          
_______________________                
Column type frequency:                 
  factor                   3           
  numeric                  10          
________________________               
Group variables            None        

W procesie preprocessingu danych jednym z kluczowych etapów była identyfikacja i usunięcie obserwacji odstających w zbiorze danych. W tym celu zdefiniowano próg dla liczby odstających wartości w danej obserwacji (outlier_threshold), który ustalono na poziomie 2. Następnie, wykorzystując metodę opartą na analizie rozstępu międzykwartylowego (IQR), zidentyfikowano wartości odstające w każdej kolumnie numerycznej zbioru danych.

Funkcja ta oblicza pierwszy (Q1) i trzeci kwartyl (Q3) dla każdej zmiennej, a następnie określa zakres wartości uznawanych za normalne, wyznaczony przez przedział [Q1−1.5⋅IQR,Q3+1.5⋅IQR]. Wartości spoza tego zakresu są oznaczane jako odstające. Na tej podstawie wygenerowano macierz logiczną, gdzie każda komórka wskazuje, czy dana wartość w zbiorze danych jest odstająca.

Dla każdej obserwacji w zbiorze danych policzono następnie liczbę odstających wartości. Obserwacje zawierające co najmniej dwie wartości odstające (zgodnie z ustalonym progiem) zostały oznaczone jako potencjalnie problematyczne. Takie obserwacje zostały wykluczone z dalszej analizy, co pozwoliło oczyścić dane i zminimalizować ich wpływ na model. Zostało usuniętych 318 rekordów.

data_scaled <- as.data.frame(lapply(data_cleaned, function(col) {
  if (is.numeric(col)) {
    rescale(col)
  } else {
    col  
  }
}))

skim(data_scaled)
── Data Summary ────────────────────────
                           Values     
Name                       data_scaled
Number of rows             3635       
Number of columns          13         
_______________________               
Column type frequency:                
  factor                   3          
  numeric                  10         
________________________              
Group variables            None       

Następnie dane zostały znormalizowane, do czego wykorzystano funkcję rescale.

Dane zostały podzielone na zbiory treningowy (70%) i testowy (30%). Podczas trenowania modelu zastosowano metodę walidacji krzyżowej, aby zapewnić jego stabilność i uogólnioną jakość. Dodatkowo, aby upewnić się, że rozkłady zmiennej celu są podobne w obu zbiorach, zwizualizowano je na poniższym wykresie gęstości.


idx <- createDataPartition(data_scaled$Volumetric.Energy, p=0.7, list=F)

train_data <- data_scaled[idx, ]
test_data <- data_scaled[-idx, ]

ctrl <- trainControl(method = "cv", number = 10)

ggplot(mapping = aes(alpha = 0.4)) + 
  geom_density(aes(x = Volumetric.Energy, fill = "red"), data = train_data) + 
  geom_density(aes(x = Volumetric.Energy, fill = "blue"), data = test_data) + 
  theme_minimal()

model<- lm(
  Volumetric.Energy~., 
  data = train_data, 
  trControl = ctrl
)

Ocena modelu

Do oceny modelu wybrano metryki R-squared, R-squared adjusted oraz RMSE. Model osiąga bardzo wysokie dopasowanie, wyjaśniając 97,57% zmienności zmiennej zależnej (R-squared). Skorygowana wartość (R-squared adjusted) wynosząca 94,46% uwzględnia liczbę predyktorów, co wskazuje na wysoką jakość modelu przy minimalnym wpływie nadmiernego dopasowania. Niska wartość RMSE (0,1788) potwierdza, że średni błąd predykcji jest niewielki w kontekście skali zmiennej zależnej, co świadczy o dużej dokładności modelu. Model jest dobrze dopasowany i precyzyjny.

model_summary <- summary(model) 
rmse_value <- rmse(test_data$Volumetric.Energy, predictions)
results <- data.frame(
  Metryka = c("R-squared", "R-squared adjusted", "RMSE"),
  Wartość = c(
    model_summary$r.squared,
    model_summary$adj.r.squared,
    round(rmse_value, 4)
  )
)

kable(
  results,
  col.names = c("Metryka", "Wartość"),
  align = "c",
  caption = "Wyniki modelu"
)
Wyniki modelu
Metryka Wartość
R-squared 0.9757357
R-squared adjusted 0.9445946
RMSE 3.1208000
NA

coefficients <- tidy(model) 

residuals <- resid(model)
fitted <- fitted(model)

residuals_fitted_plot <- ggplot(data = data.frame(residuals, fitted), aes(x = fitted, y = residuals)) +
  geom_point(alpha = 0.6, color = "blue") +  
  geom_hline(yintercept = 0, linetype = "dashed", color = "red") +
  labs(title = "Reszty a dopasowane wartości", x = "Dopasowane wartości", y = "Reszty") +
  theme_minimal()

qq_plot <- ggplot(data = data.frame(residuals), aes(sample = residuals)) +
  stat_qq(color = "blue", alpha = 0.6) +  
  stat_qq_line(color = "red") + 
  labs(title = "Wykres Q-Q dla reszt", x = "Teoretyczne kwantyle", y = "Kwantyle próby") +
  theme_minimal()

residuals_hist <- ggplot(data = data.frame(residuals), aes(x = residuals)) +
  geom_histogram(aes(y = ..density..), bins = 30, fill = "blue", alpha = 0.6) +
  geom_density(color = "red", size = 1) +
  labs(title = "Histogram reszt", x = "Reszty", y = "Gęstość") +
  theme_minimal()

predictions <- predict(model, newdata = train_data)
observed <- train_data$Volumetric.Energy
observed_predicted_plot <- ggplot(data = data.frame(observed, predictions), aes(x = observed, y = predictions)) +
  geom_point(alpha = 0.6, color = "blue") +
  geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "red") +
  labs(title = "Rzeczywiste a przewidywane wartości", x = "Rzeczywiste", y = "Przewidywane") +
  theme_minimal()

grid.arrange(residuals_fitted_plot, qq_plot, residuals_hist, observed_predicted_plot, ncol = 2)

Reszty są równomiernie rozproszone wokół poziomej linii, a ich rozkład nie wykazuje żadnych wyraźnych wzorców czy tendencji. Sugeruje to, że założenie liniowości modelu jest spełnione i świadczy o poprawnej specyfikacji modelu. Dodatkowo, wykres Q-Q dla reszt wykazuje, iż rozkład ten jest zbliżony do normalnego, potwierdzając tym samym kolejne ważne założenie regresji. Histogram reszt również potwierdza ten wniosek, prezentując symetryczny kształt typowy dla rozkładu normalnego. Kluczowym elementem oceny jakości modelu jest wykres porównujący wartości rzeczywiste i przewidywane, który pokazuje, że predykcje modelu ściśle odpowiadają rzeczywistym obserwacjom - punkty układają się wzdłuż linii o nachyleniu zbliżonym do 1, co świadczy o dobrym dopasowaniu modelu. Nieznaczne odchylenia od linii idealnej są normalne i wynikają z występowania reszt, które nie zostały w pełni wytłumaczone przez model.

coefficients <- summary(model)$coefficients
coefficients_sorted <- coefficients[order(abs(coefficients[, "Estimate"]), decreasing = TRUE), ]

datatable(coefficients_sorted, 
          options = list(pageLength = 10, 
                         autoWidth = TRUE,
                         searchHighlight = TRUE))
importance <- varImp(model)

sorted_attr_importance <- importance %>%
  arrange(desc(Overall))

sorted_attr_importance

Predykcja średniego napięcia metodą random forest

W rozdziale zostanie przeanalizowana predykcja średniego napięcia (Average Voltage) przy użyciu modelu Random Forest. Model ten pozwala uchwycić nieliniowe zależności i zidentyfikować kluczowe czynniki wpływające na napięcie. Przedstawione zostaną miary jakości oraz znaczenie zmiennych.

attributes_to_remove_rf <- colnames(select(data, c(Battery.ID, Battery.Formula, Formula.Discharge, Formula.Charge)))

W pierwszym kroku usunięto wybrane kolumny takie jak Battery.ID, Battery.Formula, Formula.Discharge, Formula.Charge, które nie wnosiły bezpośrednich informacji do modelowania.

train_data_rf <- select(data, -(attributes_to_remove_rf))
cor_thereshold <- 0.8
cor_with_target <- cor(dataset_numerical_attrs, use = "pairwise.complete.obs")[, "Average.Voltage"]
high_cor_to_target <- names(which(cor_with_target > 0.8))
train_data_rf <- train_data_rf  %>%
  select(-all_of(high_cor_to_target), Average.Voltage)

Następnie, obliczono korelację zmiennych numerycznych ze zmienną celu, zidentyfikowano i usunięto zmienne o wysokiej korelacji liniowej (przekraczającej wartość 0.8). Atrybuty, które zostały usunięte to Average.Voltage.


outlier_threshold <- 3

outlier_matrix <- as.data.frame(lapply(train_data_rf, function(column) {
  if (is.numeric(column)) {
    return(detect_outliers(column))
  } else {
    return(rep(FALSE, length(column)))
  }
}))

outlier_count <- rowSums(outlier_matrix)
removed_attrs <- nrow(train_data_rf[outlier_count >= outlier_threshold,])

data_cleaned_rf <- train_data_rf[outlier_count < outlier_threshold, ]
skim(data_cleaned_rf)
── Data Summary ────────────────────────
                           Values         
Name                       data_cleaned_rf
Number of rows             4033           
Number of columns          13             
_______________________                   
Column type frequency:                    
  factor                   2              
  numeric                  11             
________________________                  
Group variables            None           

Ze zbioru usunięto 318 rekordów.

data_rf <- train_data_rf
head(data_rf)
idx_rf <- createDataPartition(data_rf$Average.Voltage, p=0.7, list=F)

train_data_rf <- data_rf[idx, ]
test_data_rf <- data_rf[-idx, ]

ctrl <- trainControl(method = "cv", number = 10)

ggplot(mapping = aes(alpha = 0.4)) + 
  geom_density(aes(x = Average.Voltage, fill = "red"), data = train_data_rf) + 
  geom_density(aes(x = Average.Voltage, fill = "blue"), data = test_data_rf) + 
  theme_minimal()

Zbiory mają zbliżone rozkłady zmiennej celu.


model_rf <- train(
  Average.Voltage ~., 
  data = train_data_rf, 
  method = "rf", 
  metric = "RMSE",
  trControl = ctrl, 
  tuneGrid = expand.grid(mtry=3)
  )

predictions <- predict(model_rf, newdata = test_data_rf)

Ocena modelu

W celu oceny jakości modeli wykorzystano metryki RMSE, R-squared i MAE, ponieważ umożliwiają one kompleksową analizę błędów predykcji oraz dopasowania modelu do danych.


ggplot(data.frame(Actual = test_data_rf$Average.Voltage, Predicted = predictions), aes(x = Actual, y = Predicted)) +
  geom_point(alpha = 0.6, color = "blue") +
  geom_abline(slope = 1, intercept = 0, color = "red") +
  labs(title = "Wartości rzeczywiste a predykcja", x = "Rzeczywiste", y = "Przewidywane")


results <- data.frame(
  Metryka = c("RMSE", "R-squared", "MAE"),
  Wartość = c(
    model_rf$results$RMSE,     
    model_rf$results$Rsquared,
    model_rf$results$MAE
  )
)

kable(
  results,
  col.names = c("Metryka", "Wartość"),
  align = "c",
  caption = "Wyniki modelu Random Forest"
)
Wyniki modelu Random Forest
Metryka Wartość
RMSE 0.5308909
R-squared 0.8949949
MAE 0.3789240

RMSE wskazuje, że przeciętny błąd predykcji wynosi około 0.53. Atrybut przyjmuje wartości z przedziału (-7;5.5), a więc RMSE jest stosunkowo niski. Sugeruje to, że model dobrze przewiduje wartość napięcia w większości przypadków, przy umiarkowanym błędzie. Wartość \(R^2 = 0.894\) oznacza, że model wyjaśnia 89.4% zmienności zmiennej zależnej. Jest to bardzo dobry wynik, wskazujący, że model dobrze dopasowuje się do danych i większość obserwacji może być przewidziana na podstawie dostępnych cech.


importance_df <- variable_importance$importance %>%
  as.data.frame() %>%
  tibble::rownames_to_column(var = "Variable") %>%
  arrange(desc(Overall))

ggplot(importance_df, aes(x = reorder(Variable, Overall), y = Overall)) +
  geom_bar(stat = "identity", fill = "steelblue", alpha = 0.8) +
  coord_flip() +
  labs(
    title = "Ważność zmiennych (Random Forest)",
    x = "Zmienna",
    y = "Znaczenie (Overall)"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
    axis.title = element_text(size = 14),
    axis.text = element_text(size = 12)
  )

Najbardziej istotne zmienne dla predykcji modelu to energia grawimetryczna i wolumetryczna.

Źródła

„Co warto wiedzieć o ogniwach litowo‑jonowych?” 2024. https://zpe.gov.pl/a/przeczytaj/D10ElmwYT.
Henry. 2024. „The Complete Guide to Lithium Ion Battery Voltage Chart”. https://www.ufinebattery.com/blog/lithium-ion-battery-voltage-chart/.
„Litowo-jonowe a litowo-polimerowe: Szczegółowe porównanie”. 2024. https://pkcell.com/pl/porownanie-jonow-litowych-i-polimerow-litowych/.
Singh, Priyavi. 2024. „Trends in the Gravimetric and Volumetric Energy Densities of Lithium-ion Batteries Over the Past Decade”. International Journal of Science and Research.
LS0tDQp0aXRsZTogIlByb2dyYW1vd2FuaWUgdyBSOiBQcm9qZWt0Ig0Kc3VidGl0bGU6ICAgIkFuYWxpemEgYmF6eSBkYW55Y2ggbWF0ZXJpYcWCw7N3IHd5a29yenlzdHl3YW55Y2ggdyB0d29yemVuaXUgYmF0ZXJpaSINCmF1dGhvcjogS2xhdWRpYSBLb3dhbHNrYQ0KZGF0ZTogImByIGZvcm1hdChTeXMudGltZSgpLCAnJWQgJUIgJVknKWAiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgbGluay1jaXRhdGlvbnM6IHRydWUNCiAgICBjb2RlX2ZvbGRpbmc6ICJoaWRlIg0KYmlibGlvZ3JhcGh5OiAicmVmZXJlbmNlcy5iaWIiDQpub2NpdGU6ICdAKicNCmxhbmc6IHBsDQplZGl0b3Jfb3B0aW9uczogDQogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUNCi0tLQ0KDQoNCiMgV3N0xJlwDQpDZWxlbSBwcm9qZWt0dSBiecWCYSBhbmFsaXphIHpiaW9ydSBkYW55Y2ggZG90eWN6xIVjZWdvIG1hdGVyaWHFgsOzdyB3eWtvcnp5c3R5d2FueWNoIHcgYmF0ZXJpYWNoLCB6IHd5a29yenlzdGFuaWVtIGrEmXp5a2EgUi4gRGFuZSB0ZSBwb2Nob2R6xIUgeiBiYXp5IE1hdGVyaWFscyBQcm9qZWN0IOKAkyBpbmljamF0eXd5IERlcGFydGFtZW50dSBFbmVyZ2lpIFVTQSwga3TDs3JhIGRvc3RhcmN6YSBvdHdhcnRlIHphc29ieSBkbyBiYWRhbmlhIHfFgmHFm2Npd2/Fm2NpIGkgemFzdG9zb3dhxYQgbWF0ZXJpYcWCw7N3Lg0KDQpaYmnDs3IgdGVuIHphd2llcmEgc3pjemVnw7PFgm93ZSBpbmZvcm1hY2plIG8gc2vFgmFkemllIGNoZW1pY3pueW0gaSBwYXJhbWV0cmFjaCB3eWRham5vxZtjaW93eWNoIG1hdGVyaWHFgsOzdyBzdG9zb3dhbnljaCB3IGJhdGVyaWFjaC4gQW5hbGl6YSBwb3p3YWxhIGxlcGllaiB6cm96dW1pZcSHIGljaCB3xYJhxZtjaXdvxZtjaSwgY28gd3NwaWVyYSByb3p3w7NqIGlubm93YWN5am55Y2ggdGVjaG5vbG9naWkgbWFnYXp5bm93YW5pYSBlbmVyZ2lpLiBSYXBvcnQgcHJ6ZWRzdGF3aWEgd3luaWtpIHByemVwcm93YWR6b255Y2ggYmFkYcWEIG9yYXoga2x1Y3pvd2Ugd25pb3NraS4NCg0KUmFwb3J0IHByemVkc3Rhd2lhIGtvbXBsZWtzb3fEhSBhbmFsaXrEmSB6YmlvcnUgZGFueWNoIGRvdHljesSFY2VnbyB3xYJhxZtjaXdvxZtjaSBtYXRlcmlhxYLDs3cgZG8gYmF0ZXJpaSBvcmF6IHR3b3J6ZW5pZSBtb2RlbGkgcHJlZHlrY3lqbnljaC4gVyBwaWVyd3N6ZWogY3rEmcWbY2kgZG9rb25hbm8gc3pjemVnw7PFgm93ZWogYW5hbGl6eSByb3prxYJhZMOzdyBwb3N6Y3plZ8OzbG55Y2ggYXRyeWJ1dMOzdywgY28gcG96d29sacWCbyBuYSB6cm96dW1pZW5pZSBpY2ggY2hhcmFrdGVyeXN0eWtpIGkgemlkZW50eWZpa293YW5pZSBwb3RlbmNqYWxueWNoIHphbGXFvG5vxZtjaS4gTmFzdMSZcG5pZSBza29uc3RydW93YW5vIG1vZGVsZSBwcmVkeWtjeWpuZSBkbGEgem1pZW5ueWNoLCB0YWtpY2ggamFrIGVuZXJnaWEgZ3Jhd2ltZXRyeWN6bmEgb3JheiDFm3JlZG5pZSBuYXBpxJljaWUuDQoNClJhcG9ydCB6YXdpZXJhIHRha8W8ZSB3aXp1YWxpemFjamUsIGt0w7NyZSB1xYJhdHdpYWrEhSBpbnRlcnByZXRhY2rEmSB3eW5pa8Ozdywgb3JheiBtZXRyeWtpIG9jZW55IG1vZGVsaSwgdGFraWUgamFrIFItc3F1YXJlZCwgUk1TRSBpIE1BRSwgY28gdW1vxbxsaXdpYSBvYmlla3R5d27EhSBvY2VuxJkgaWNoIHNrdXRlY3pub8WbY2kuIA0KDQpXIHJhbWFjaCBhbmFsaXp5IG9ka3J5dG8sIMW8ZSBuYWpjesSZxZtjaWVqIHd5a29yenlzdHl3YW55bSBqb25lbSBnxYLDs3dueW0gamVzdCBsaXQuIFp3aXp1YWxpem93YW5vIHJvemvFgmFkeSBhdHJ5YnV0w7N3LCBwb3p3YWxhamFjxJkgbmEga29tcGxla3Nvd8SFIG9jZW7EmSBpY2ggd8WCYcWbY2l3b8WbY2kuIFBvendvbGnFgm8gdG8gbmEgb2RrcnljaWUgacW8IG5pZSBtYSB3IHpiaW9yemUgem5hY3puaWUgZG9taW51asSFY3ljaCB3em9yw7N3IGNoZW1pY3pueWNoIG1hdGVyaWHFgnUgYmF0ZXJpaSwgd3lrb3J6eXN0eXdhbnljaCBqZXN0IHdpZWxlIHVuaWthbG55Y2ggd3pvcsOzdy4gUG9kc3Vtb3dhbm8gbmFqd2HFvG5pZWpzemUgc3Bvc3RyemXFvGVuaWEgY2hhcmFrdGVyeXN0eWsgbWF0ZXJpYcWCw7N3IGRsYSByw7PFvG55Y2ggZ3J1cCBqb251IGfFgsOzd25lZ28uDQoNCk5hIHBvZHN0YXdpZSBtYWNpZXJ6eSBrb3JlbGFjamkgd3l6bmFjem9ubyBhdHJ5YnV0eSBvIG5handpxJlrc3plaiB6YWxlxbxub8WbY2kuIFPEhSB0byBlbmVyZ2lhIGdyYXdpbWV0cnljem5hIGkgd29sdW1ldHJ5Y3puYSBvcmF6IHBvamVtbm/Fm8SHIGdyYXdpbWV0cnljem5hIGkgd29sdW1ldHJ5Y3puYS4gDQpTa29uc3RydW93YW5vIG1vZGVsZSBwcmVkeWtjeWpuZSB3eWtvcnp5c3R1asSFYyBtZXRvZMSZIHJlZ3Jlc2ppIGxpbmlvd2VqIGkgcmFuZG9tIGZvcmVzdC4gV3N0xJlwbmUgZGFuZSB6b3N0YcWCeSBwb2RkYW5lIHByZXByb2Nlc3Npbmdvd2kgb3BhcnRlbXUgbyBraWxrYSBldGFww7N3IHRha2ljaCBqYWsgdXN1d2FuaWUgd2FydG/Fm2NpIG9kc3RhasSFY3ljaCwgbm9ybWFsaXphY2phLiBXeWJyYW5lIG1ldHJ5a2kgcG96d29sacWCeSBuYSBvY2VuxJkgbW9kZWxpLCBrdMOzcmUgd3lrYXphxYJ5IHNpxJkgd3lzb2vEhSBqYWtvxZtjacSFIHByZWR5a2NqaS4gDQoNCg0KYGBge3Igc2V0dXAsIGluY2x1ZGUgPSBGQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldCh3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgb3V0LndpZHRoID0gIjEwMCUiKQ0Kc2V0LnNlZWQoMTEwOCkNCmBgYA0KDQojIyBXeWtvcnp5c3RhbmUgYmlibGlvdGVraQ0KDQpgYGB7ciBsaWJyYXJpZXN9DQpsaWJyYXJ5KHNraW1yKQ0KbGlicmFyeShjb3JycGxvdCkNCmxpYnJhcnkoR0dhbGx5KQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkocGxvdGx5KQ0KbGlicmFyeSgiSG1pc2MiKQ0KbGlicmFyeShtbGJlbmNoKQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkocmxhbmcpDQpsaWJyYXJ5KGtuaXRyKQ0KbGlicmFyeShEVCkNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGdyaWRFeHRyYSkNCmxpYnJhcnkoaGVyZSkNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShzY2FsZXMpDQpsaWJyYXJ5KHBhdGNod29yaykNCmxpYnJhcnkoTWV0cmljcykNCmxpYnJhcnkoYnJvb20pDQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkNCmBgYA0KDQojIyBKYWsgd3lnbMSFZGFqxIUgZGFuZT8NCg0KYGBge3IgbG9hZCBkYXRhLCBjYWNoZSA9IFRSVUUsIHJlc3VsdHM9RkFMU0V9DQp1cmxmaWxlPSJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vS2xhdWRpYUsvQmF0dGVyeS1tYXRlcmlhbHMtYW5hbHlzaXMvcmVmcy9oZWFkcy9tYXN0ZXIvbXBfYmF0dGVyaWVzLmNzdiINCmRhdGEgPC0gcmVhZC5jc3YodXJsKHVybGZpbGUpKQ0KDQpkYXRhJFN0ZXBzIDwtIGFzLmZhY3RvcihkYXRhJFN0ZXBzKQ0KZGF0YSRXb3JraW5nLklvbiA8LSBhcy5mYWN0b3IoZGF0YSRXb3JraW5nLklvbikNCmRhdGEkRm9ybXVsYS5DaGFyZ2UgPC0gYXMuZmFjdG9yKGRhdGEkRm9ybXVsYS5DaGFyZ2UpDQpkYXRhJEZvcm11bGEuRGlzY2hhcmdlIDwtIGFzLmZhY3RvcihkYXRhJEZvcm11bGEuRGlzY2hhcmdlKQ0KYGBgDQoNClcgY2VsdSB6aWx1c3Ryb3dhbmlhIHphd2FydG/Fm2NpIHpiaW9ydSBkYW55Y2gsIHd5xZt3aWV0bG9ubyBraWxrYSBwaWVyd3N6eWNoIHdpZXJzenkgemJpb3J1LCBhYnkgemFwcmV6ZW50b3dhxIcgc3RydWt0dXLEmSB0YWJlbGksIG5hend5IHptaWVubnljaCBvcmF6IHByenlrxYJhZHkgd2FydG/Fm2NpLCBrdMOzcmUgb25lIHByenlqbXVqxIUuDQoNCmBgYHtyIGRpc3BsYXktZGF0YSwgZWNobz1GQUxTRX0NCmhlYWQoZGF0YSkNCmBgYA0KDQojIyBBdHJ5YnV0eQ0KDQpXIHBvbmnFvHN6ZWogdGFiZWxpIHByemVkc3Rhd2lvbm8gem5hamR1asSFY2Ugc2nEmSB3IHpiaW9yemUgYXRyeWJ1dHkgb3JheiBpY2ggZGVmaW5pY2plLiANCg0KfCBBdHJ5YnV0ICAgICAgICAgICB8IE9waXMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfA0KfCAqKkJhdHRlcnkgSUQqKiAgICAgICAgICAgICAgICB8IElkZW50eWZpa2F0b3IgYmF0ZXJpaS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8ICoqQmF0dGVyeSBGb3JtdWxhKiogICAgICAgICAgIHwgV3rDs3IgY2hlbWljem55IG1hdGVyaWHFgnUgYmF0ZXJpaS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAqKldvcmtpbmcgSW9uKiogICAgICAgICAgICAgICB8IEfFgsOzd255IGpvbiwga3TDs3J5IG9kcG93aWFkYSB6YSB0cmFuc3BvcnQgxYJhZHVua3UgdyBiYXRlcmlpLiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAqKkZvcm11bGEgQ2hhcmdlKiogICAgICAgICAgICB8IFd6w7NyIGNoZW1pY3pueSBtYXRlcmlhxYJ1IGJhdGVyaWkgdyBzdGFuaWUgbmHFgmFkb3dhbnltLiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8ICoqRm9ybXVsYSBEaXNjaGFyZ2UqKiAgICAgICAgIHwgV3rDs3IgY2hlbWljem55IG1hdGVyaWHFgnUgYmF0ZXJpaSB3IHN0YW5pZSByb3rFgmFkb3dhbnltLiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwgKipNYXggRGVsdGEgVm9sdW1lKiogICAgICAgICAgfCBabWlhbmEgb2JqxJl0b8WbY2kgdyAlIGRsYSBkYW5lZ28ga3Jva3UgbmFwacSZY2lhIHphIHBvbW9jxIUgd3pvcnUgOiBtYXgoY2hhcmdlLCBkaXNjaGFyZ2UpL21pbihjaGFyZ2UsIGRpc2NoYXJnZSkgLTEuIHwNCnwgKipBdmVyYWdlIFZvbHRhZ2UqKiAgICAgICAgICAgfCDFmnJlZG5pZSBuYXBpxJljaWUgZGxhIHBvc3pjemVnw7NsbmVnbyBrcm9rdSBuYXBpxJljaWEuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwgKipHcmF2aW1ldHJpYyBDYXBhY2l0eSoqICAgICAgfCBQb2plbW5vxZvEhyBncmF3aW1ldHJ5Y3puYSwgY3p5bGkgaWxvxZvEhyBlbmVyZ2lpIG5hIGplZG5vc3RrxJkgbWFzeSAobUFoL2cpLiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8ICoqVm9sdW1ldHJpYyBDYXBhY2l0eSoqICAgICAgIHwgUG9qZW1ub8WbxIcgd29sdW1ldHJ5Y3puYSwgY3p5bGkgaWxvxZvEhyBlbmVyZ2lpIG5hIGplZG5vc3RrxJkgb2JqxJl0b8WbY2kgKG1BaC9jbcKzKS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAqKkdyYXZpbWV0cmljIEVuZXJneSoqICAgICAgICB8IEfEmXN0b8WbxIcgZW5lcmdpaSB3IG9kbmllc2llbml1IGRvIG1hc3kgYmF0ZXJpaSAoV2gva2cpLiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8ICoqVm9sdW1ldHJpYyBFbmVyZ3kqKiAgICAgICAgIHwgR8SZc3RvxZvEhyBlbmVyZ2lpIHcgb2RuaWVzaWVuaXUgZG8gb2JqxJl0b8WbY2kgYmF0ZXJpaSAoV2gvTCkuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAqKkF0b21pYyBGcmFjdGlvbiBDaGFyZ2UqKiAgICB8IFVkemlhxYIgYXRvbW93eSBza8WCYWRuaWvDs3cgdyBzdGFuaWUgbmHFgmFkb3dhbnltLiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAqKkF0b21pYyBGcmFjdGlvbiBEaXNjaGFyZ2UqKiB8IFVkemlhxYIgYXRvbW93eSBza8WCYWRuaWvDs3cgdyBzdGFuaWUgcm96xYJhZG93YW55bS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAqKlN0YWJpbGl0eSBDaGFyZ2UqKiAgICAgICAgICB8IFdza2HFum5payBzdGFiaWxub8WbY2kgbWF0ZXJpYcWCdSB3IHN0YW5pZSBuYcWCYWRvd2FueW0uICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAqKlN0YWJpbGl0eSBEaXNjaGFyZ2UqKiAgICAgICB8IFdza2HFum5payBzdGFiaWxub8WbY2kgbWF0ZXJpYcWCdSB3IHN0YW5pZSByb3rFgmFkb3dhbnltLiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAqKlN0ZXBzKiogICAgICAgICAgICAgICAgICAgICB8IExpY3piYSBvZHLEmWJueWNoIGtyb2vDs3cgbmFwacSZY2lhIG9kIHBlxYJuZWdvIG5hxYJhZG93YW5pYSBkbyByb3rFgmFkb3dhbmEsIG9wYXJ0YSBuYSBzdGFiaWxueWNoIHN0YW5hY2ggcG/Fm3JlZG5pY2guICAgfA0KfCAqKk1heCBWb2x0YWdlIFN0ZXAqKiAgICAgICAgICB8IE1ha3N5bWFsbmEgYmV6d3pnbMSZZG5hIHLDs8W8bmljYSBtacSZZHp5IHPEhXNpZWRuaW1pIGtyb2thbWkgbmFwacSZY2lhLiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQoNCiMjIENoYXJha3RlcnlzdHlrYSB6YmlvcnUgZGFueWNoDQoNCioqWmJpw7NyIGRhbnljaCB6YXdpZXJhIGByIG5jb2woZGF0YSlgIGF0cnlidXTDs3cgaSBgciBucm93KGRhdGEpYCByZWtvcmTDs3cuKioNCg0KYGBge3IgY291bnQtbmEtdmFsdWVzLCBlY2hvPUZBTFNFfQ0Kc3VtX25hIDwtIHN1bShpcy5uYShkYXRhKSkNCmBgYA0KDQoqKlN1bWEgYnJha3VqxIVjeWNoIHdhcnRvxZtjaSB3IHpiaW9yemU6IGByIHN1bV9uYWAuKioNCg0KYGBge3IgbWlzc2luZy12YWx1ZXN9DQprYWJsZShjb2xTdW1zKGlzLm5hKGRhdGEpKSwgY29sLm5hbWVzID0gYygiTGljemJhIGJyYWt1asSFY3ljaCB3YXJ0b8WbY2kiKSwgY2FwdGlvbiA9ICJMaWN6YmEgYnJha3VqxIVjeWNoIHdhcnRvxZtjaSB3IGtvbHVtbmFjaCIpDQpgYGANCg0KKipTdW1hIGR1cGxpa2F0w7N3OiBgciBzdW0oZHVwbGljYXRlZChkYXRhKSlgLioqDQoNClpiacOzciBkYW55Y2ggbmllIHphd2llcmEgYnJha3VqxIVjeWNoIHdhcnRvxZtjaSBhbmkgZHVwbGlrYXTDs3csIHdpxJljIGRhbmUgbmllIHd5bWFnYWrEhSBjenlzemN6ZW5pYS4NCg0KYGBge3IgY29sdW1uLXR5cGVzLXN1bW1hcnl9DQpza2ltX3N1bW1hcnkgPC0gc2tpbShkYXRhKQ0KDQpudW1fY2hhcmFjdGVyIDwtIHNraW1fc3VtbWFyeSAlPiUgZmlsdGVyKHNraW1fdHlwZSA9PSAiY2hhcmFjdGVyIikgJT4lIG5yb3coKQ0KbnVtX251bWVyaWMgPC0gc2tpbV9zdW1tYXJ5ICU+JSBmaWx0ZXIoc2tpbV90eXBlID09ICJudW1lcmljIikgJT4lIG5yb3coKQ0KbnVtX2xvZ2ljYWwgPC0gc2tpbV9zdW1tYXJ5ICU+JSBmaWx0ZXIoc2tpbV90eXBlID09ICJsb2dpY2FsIikgJT4lIG5yb3coKQ0KYGBgDQoNClpiacOzciB6YXdpZXJhOiBcDQotIGtvbHVtbnkgem5ha293ZTogYHIgbnVtX2NoYXJhY3RlcmAsIFwNCi0ga29sdW1ueSBudW1lcnljem5lOiBgciBudW1fbnVtZXJpY2AsXA0KLSBrb2x1bW55IGxvZ2ljem5lOiBgciBudW1fbG9naWNhbGAgXA0KDQpgYGB7cn0NCnNraW1fc3VtbWFyeQ0KYGBgDQoNCg0KYGBge3IgbnVtZXJpY2FsLWF0dHJpYnV0ZXMsIGVjaG89RkFMU0V9DQpudW1lcmljYWxfYXR0cnMgPC0gZGF0YSAlPiUgc2VsZWN0X2lmKGlzLm51bWVyaWMpICU+JSBjb2xuYW1lcw0KYGBgDQoNCmBgYHtyIHByZXR0eS10YWJsZSwgIGVjaG89RkFMU0V9DQpwcmV0dHlUYWJsZSA8LSBmdW5jdGlvbih0YWJsZV9kZiwgcm91bmRfZGlnaXRzPTIpIHsgDQogIERUOjpkYXRhdGFibGUodGFibGVfZGYsIHN0eWxlPSJib290c3RyYXAiLCBmaWx0ZXIgPSAidG9wIiwgcm93bmFtZXMgPSBGQUxTRSwgZXh0ZW5zaW9ucyA9ICJCdXR0b25zIiwgb3B0aW9ucyA9IGxpc3QoZG9tID0gJ0JmcnRpcCcsc2Nyb2xsWCA9IFRSVUUsIGF1dG9XaWR0aCA9IFRSVUUpKSAlPiUgDQogICAgZm9ybWF0Um91bmQobmFtZXMoZHBseXI6OnNlbGVjdF9pZih0YWJsZV9kZiwgaXMubnVtZXJpYykpLCByb3VuZF9kaWdpdHMpDQp9DQpgYGANCg0KIyBBbmFsaXphIHpiaW9ydSBkYW55Y2gNCg0KVyB0ZWogY3rEmcWbY2kgem9zdGFuaWUgcHJ6ZXByb3dhZHpvbmEgYW5hbGl6YSB3YXJ0b8WbY2kgYXRyeWJ1dMOzdyB3IHpiaW9yemUgZGFueWNoLiBDZWxlbSB0ZWogYW5hbGl6eSBqZXN0IHpyb3p1bWllbmllIHJvemvFgmFkdSwgem1pZW5ub8WbY2kgb3JheiBrbHVjem93eWNoIGNlY2ggcG9zemN6ZWfDs2xueWNoIGF0cnlidXTDs3csIGNvIHBvendvbGkgbmEgbGVwc3rEhSBpbnRlcnByZXRhY2rEmSBkYW55Y2guIEFuYWxpemEgb2Jlam1pZSByw7PFvG5lIG1ldG9keSB3aXp1YWxpemFjamksIHRha2llIGphayBoaXN0b2dyYW15LCB3eWtyZXN5IGfEmXN0b8WbY2kgb3JheiB3eWtyZXN5IHB1ZGXFgmtvd2UsIGt0w7NyZSB1bW/FvGxpd2nEhSBzenlia2llIHd5Y2h3eWNlbmllIGlzdG90bnljaCB0cmVuZMOzdywgd2FydG/Fm2NpIG9kc3RhasSFY3ljaCBvcmF6IGNoYXJha3RlcnlzdHlraSByb3prxYJhZHUgZGFueWNoLg0KDQojIyBSb3prxYJhZHkgYXRyeWJ1dMOzdw0KDQojIyMgQXRyeWJ1dHkgbnVtZXJ5Y3puZQ0KDQpTZWtjamEgb2Jlam11amUgd2l6dWFsaXphY2rEmSByb3prxYJhZMOzdyB3YXJ0b8WbY2kgZGxhIGF0cnlidXTDs3cgbnVtZXJ5Y3pueWNoLiBXIGfDs3JuZWogY3rEmcWbY2kgem5hamR1amUgc2nEmSBoaXN0b2dyYW0sIGt0w7NyeSBpbHVzdHJ1amUgY3rEmXN0b8WbxIcgd3lzdMSZcG93YW5pYSByw7PFvG55Y2ggd2FydG/Fm2NpIGF0cnlidXR1IHByenlwb3J6xIVka293YW55Y2ggZG8gb2tyZcWbbG9uZWogbGljemJ5IHByemVkemlhxYLDs3cuIA0KUG9tYXJhxYRjem93eSBrb2xvciByZXByZXplbnR1amUgbGluacSZIGfEmXN0b8WbY2kgcm96a8WCYWR1ICh0encuIGRlbnNpdHkgcGxvdCkuIFd5a3JlcyBnxJlzdG/Fm2NpIGplc3QgdcW8eXdhbnkgZG8gd2l6dWFsaXphY2ppIGtzenRhxYJ0dSByb3prxYJhZHUgZGFueWNoLCBwb3p3YWxhasSFYyBuYSBsZXBzemUgenJvenVtaWVuaWUgamVnbyBmb3JteSB3IHBvcsOzd25hbml1IGRvIGhpc3RvZ3JhbXUuDQpOYSB3eWtyZXNpZSB6bmFqZHVqZSBzacSZIHLDs3duaWXFvCBjemVyd29uYSBsaW5pYSwgb3puYWN6YWrEhWNhIMWbcmVkbmnEhSB3YXJ0b8WbxIcgYXRyeWJ1dHUsIG9yYXogesOzxYJ0YSBvem5hY3phasSFY2EgbWVkaWFuxJksIGNvIHBvendhbGEgbmEgc3p5YmvEhSBvY2VuxJkgamVnbyBjZW50cmFsbmVnbyBwb8WCb8W8ZW5pYS5cDQpQb25pxbxlaiB6bmFqZHVqZSBzacSZIHd5a3JlcyBwdWRlxYJrb3d5ICh0encuIGJveHBsb3QpLCBrdMOzcnkgd2l6dWFsaXp1amUgcm96cHJvc3plbmllIHdhcnRvxZtjaSBpIHBvendhbGEgbmEgaWRlbnR5ZmlrYWNqxJkgd2FydG/Fm2NpIG9kc3RhasSFY3ljaC4NCg0KYGBge3J9DQoNCmZvciAoYXR0cl9uYW1lIGluIG51bWVyaWNhbF9hdHRycykgew0KICBpZiAoaXMubnVtZXJpYyhkYXRhW1thdHRyX25hbWVdXSkpIHsNCiAgICBoaXN0X3BsdCA8LSBkYXRhICU+JSANCiAgICAgIGdncGxvdChtYXBwaW5nID0gYWVzX3N0cmluZyh4ID0gYXR0cl9uYW1lKSkgKyANCiAgICAgIGdlb21faGlzdG9ncmFtKGFlcyh5ID0gLi5kZW5zaXR5Li4pLCBiaW5zID0gMTAwLCBmaWxsID0gIm1pZG5pZ2h0Ymx1ZSIsIGFscGhhID0gMC43KSArDQogICAgICBnZW9tX2RlbnNpdHkoY29sb3IgPSAiZGFya29yYW5nZSIsIHNpemUgPSAxKSArDQogICAgICAjIEFkZCBsaW5lcyBmb3IgbWVhbiBhbmQgbWVkaWFuDQogICAgICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gbWVhbihnZXQoYXR0cl9uYW1lKSwgbmEucm0gPSBUUlVFKSwgY29sb3IgPSAnTWVhbicpLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBzaXplID0gMS4zKSArDQogICAgICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gbWVkaWFuKGdldChhdHRyX25hbWUpLCBuYS5ybSA9IFRSVUUpLCBjb2xvciA9ICdNZWRpYW4nKSwgbGluZXR5cGUgPSAiZGFzaGVkIiwgc2l6ZSA9IDEuMykgKw0KICAgICAgeGxhYihhdHRyX25hbWUpICsNCiAgICAgIHlsYWIoIkZyZXF1ZW5jeSIpICsNCiAgICAgIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lID0gIiIsIHZhbHVlcyA9IGMoTWVhbiA9ICJyZWQiLCBNZWRpYW4gPSAieWVsbG93IikpICsNCiAgICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC45LCAwLjkpLCBsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkNCiAgICANCiAgICBib3hfcGx0IDwtIGRhdGEgJT4lIA0KICAgICAgZ2dwbG90KGFlc19zdHJpbmcoeCA9IGF0dHJfbmFtZSwgeSA9IDEpKSArDQogICAgICBnZW9tX2JveHBsb3QoZmlsbCA9ICIjRTY5RjAwIiwgY29sb3IgPSAiZ3JheTIzIiwgYWxwaGEgPSAwLjcpICsNCiAgICAgIHhsYWIoYXR0cl9uYW1lKSArDQogICAgICB5bGFiKCIiKQ0KDQogICAgY29tYmluZWRfcGxvdCA8LSAoaGlzdF9wbHQgLyBib3hfcGx0KSArDQogICAgICBwbG90X2Fubm90YXRpb24odGl0bGUgPSBwYXN0ZSgiRGlzdHJpYnV0aW9uIG9mIiwgYXR0cl9uYW1lKSwNCiAgICAgICAgICAgICAgICAgICAgICB0aGVtZSA9IHRoZW1lKA0KICAgICAgICAgICAgICAgICAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpKQ0KICAgIA0KICAgIHByaW50KGNvbWJpbmVkX3Bsb3QpDQogICAgZ3JpZDo6Z3JpZC5saW5lcyh4ID0gYygwLCAxKSwgeSA9IGMoMCwgMCksIGdwID0gZ3JpZDo6Z3Bhcihjb2wgPSAiYmxhY2siLCBsdHkgPSAyKSkNCiAgfSBlbHNlIHsNCiAgICBtZXNzYWdlKHBhc3RlKGF0dHJfbmFtZSwgImlzIG5vdCBudW1lcmljIGFuZCB3aWxsIGJlIHNraXBwZWQuIikpDQogIH0NCn0NCg0KDQpgYGANCg0KDQpBbmFsaXp1asSFYyByb3prxYJhZHkgem1pZW5ueWNoLCBtb8W8bmEgemF1d2HFvHnEhyBraWxrYSBrbHVjem93eWNoIHRlbmRlbmNqaS4gV2nEmWtzem/Fm8SHIHptaWVubnljaCBjaGFyYWt0ZXJ5enVqZSBzacSZIHNpbG5pZSBza2/Fm255bWkgcm96a8WCYWRhbWkgeiBkdcW8xIUga29uY2VudHJhY2rEhSB3YXJ0b8WbY2kgYmxpc2tvIHplcmEgb3JheiBvZ29uYW1pIHd5ZMWCdcW8YWrEhWN5bWkgc2nEmSB3IGtpZXJ1bmt1IHdhcnRvxZtjaSBtYWtzeW1hbG55Y2guIFRha2llIHJvemvFgmFkeSBzdWdlcnVqxIUgaXN0bmllbmllIHpuYWN6xIVjZWogbGljemJ5IG9ic2Vyd2FjamkgeiBuaXNraW1pIHdhcnRvxZtjaWFtaSBvcmF6IG5pZWxpY3puZSBwcnp5cGFka2kgZWtzdHJlbWFsbnljaCB3eW5pa8Ozdy4NCg0KTmEgaGlzdG9ncmFtYWNoIG1vxbxuYSB6YXV3YcW8ecSHIHd5cmHFum5lIHBpa2kgZGxhIHdhcnRvxZtjaSBjZW50cmFsbnljaCB3IG5pZWt0w7NyeWNoIHByenlwYWRrYWNoLCBhIGxpbmllIGfEmXN0b8WbY2kgcG9tYWdhasSFIHV3aWRvY3puacSHIGtzenRhxYJ0IHR5Y2ggcm96a8WCYWTDs3cuIMWacmVkbmlhIChjemVyd29uYSBsaW5pYSBwcnplcnl3YW5hKSBpIG1lZGlhbmEgKMW8w7PFgnRhIGxpbmlhIHByemVyeXdhbmEpIGN6xJlzdG8gem5hamR1asSFIHNpxJkgYmFyZHpvIGJsaXNrbyBzaWViaWUsIGNvIHdza2F6dWplIG5hIHVtaWFya293YW7EhSBzeW1ldHJpxJkgdyBjesSZxZtjaSB6bWllbm55Y2guIE5pZW1uaWVqIGplZG5haywgdyBwcnp5cGFka3Ugbmlla3TDs3J5Y2ggem1pZW5ueWNoLCB0YWtpY2ggamFrIOKAnkdyYXZpbWV0cmljLkNhcGFjaXR54oCdIGN6eSDigJ5Wb2x1bWV0cmljLkNhcGFjaXR54oCdLCByw7PFvG5pY2EgbWnEmWR6eSDFm3JlZG5pxIUgYSBtZWRpYW7EhSBzdWdlcnVqZSB3cMWCeXcgd2FydG/Fm2NpIHNrcmFqbnljaCBuYSByb3prxYJhZC4NCg0KV3lrcmVzeSBwdWRlxYJrb3dlIHV6dXBlxYJuaWFqxIUgYW5hbGl6xJksIHV3aWRhY3puaWFqxIVjIG9iZWNub8WbxIcgbGljem55Y2ggd2FydG/Fm2NpIG9kc3RhasSFY3ljaCB3IHdpxJlrc3pvxZtjaSB6bWllbm55Y2guIE9ic2Vyd2FjamUgb2RzdGFqxIVjZSBzxIUgc3pjemVnw7NsbmllIHdpZG9jem5lIGRsYSB6bWllbm55Y2ggdGFraWNoIGphayDigJ5HcmF2aW1ldHJpYy5FbmVyZ3nigJ0gY3p5IOKAnlZvbHVtZXRyaWMuRW5lcmd54oCdLg0KDQpEbGEgcG9zemN6ZWfDs2xueWNoIGF0cnlidXTDs3cgbW/FvG5hIHd5Y2nEhWduxIXEhyB3bmlvc2tpLCDFvGUgd2nEmWtzem/Fm8SHIHdhcnRvxZtjaSBrb25jZW50cnVqZSBzacSZIHcgemFrcmVzaWU6DQoNCi0gTWF4LkRlbHRhLlZvbHVtZSAtIGtvbmNlbnRyYWNqYSB3YXJ0b8WbY2kgKipibGlza28gMCoqLCBuaWVsaWN6bmUgd2FydG/Fm2NpIG9kc3RhasSFY2UgaSBleHRyZW1hbG5lDQotIEF2ZXJhZ2UuVm9sdGFnZSAtICAqKjIgLSA0KiosIHJvemvFgmFkIGplc3QgYXN5bWV0cnljem55IHplIHd6Z2zEmWR1IG5hIGR1xbzEhSBsaWN6YsSZIHdhcnRvxZtjaSBvZHN0YWrEhWN5Y2gNCi0gR3JhdmltZXRyaWMuQ2FwYWNpdHkgLSAqKjg4IC0gMjAwKiosIGxpY3puZSB3YXJ0b8WbY2kgb2RzdGFqxIVjZSwgcm96a8WCYWQgeiBkxYJ1Z2luIG9nb25lbSBwbyBwcmF3ZWogc3Ryb25pZSANCi0gVm9sdW1ldHJpYyBDYXBhY2l0eSAtICoqMzEwIC0gNzIwKiosIHJvemvFgmFkIHogZHdvbWEgZG9taW5hbnRhbWkNCi0gR3JhdmltZXRyaWMgRW5lcmd5IC0gKioyMTAgLSA2MTUqKiwgcm96a8WCYWQgeiBkxYJ1Z2luIG9nb25lbSBwbyBwcmF3ZWogc3Ryb25pZQ0KLSBWb2x1bWV0cmljIEVuZXJneSAtICAqKjgyMCAtIDIyNTAqKiwgcm96a8WCYWQgeiBkxYJ1Z2luIG9nb25lbSBwbyBwcmF3ZWogc3Ryb25pZQ0KLSBBdG9taWMgRnJhY3Rpb24gQ2hhcmdlIC0gKiowIC0gMC4wNSoqLCDFm3JlZG5pYSBtb2NubyBwcnplc3VuacSZdGEgbmEgcHJhd28gb2QgbWVkaWFueSwgY28gbW/FvGUgYnnEhyBzcG93b2Rvd2FuZSBsaWN6bnltaSBwcnp5cGFka2FtaSBvZHN0YWrEhWN5bWkNCi0gQXRvbWljIEZyYWN0aW9uIERpc2NoYXJnZSAtICoqMC4wODYgLSAwLjIqKg0KLSBTdGFiaWxpdHkuQ2hhcmdlIC0ga29uY2VudHJhY2phIHcgemFrcmVzaWUgKiowLjAzIC0gMC4xMyoqLCBsaWN6bmUgd2FydG/Fm2NpIG9kc3RhasSFY2UsIHJvemvFgmFkIGFzeW1ldHJ5Y3pueSB6IGTFgnVnaW0gb2dvbmVtIHBvIHByYXdlaiBzdHJvbmllDQotIFN0YWJpbGl0eS5EaXNjaGFyZ2UgLSBrb25jZW50cmFjamEgdyB6YWtyZXNpZSAqKjAuMDIgLSAwLjA5KiosIGxpY3puZSB3YXJ0b8WbY2kgb2RzdGFqxIVjZSwgcm96a8WCYWQgYXN5bWV0cnljem55IHogZMWCdWdpbSBvZ29uZW0gcG8gcHJhd2VqIHN0cm9uaWUNCi0gTWF4IFZvbHRhZ2UgU3RlcCAtIHNpbG5hIGtvbmNlbnRyYWNqYSB3YXJ0b8WbY2kgKipibGlza28gMCoqDQoNCg0KIyMjIEfFgsOzd25lIGpvbnkNCg0KVGFiZWxhIHByemVkc3Rhd2lhIG1lZGlhbnkgd2FydG/Fm2NpIGF0cnlidXLDs3cgbnVtZXJ5Y3pueWNoLCBkbGEgcG9zemN6ZWfDs2xueWNoIGdydXAgZ8WCw7N3bmVnbyBqb251IHRyYW5zcG9ydHVqxIVjZWdvIMWCYWR1bmVrIChXb3JraW5nIElvbikuDQoNCmBgYHtyIGlvbl9tZWFuX3N1bW1hcnl9DQoNCmlvbl9tZWFuX3N1bSA8LSAgZGF0YSAlPiUNCiAgICBzZWxlY3QoV29ya2luZy5Jb24sIE1heC5EZWx0YS5Wb2x1bWU6U3RhYmlsaXR5LkRpc2NoYXJnZSkgJT4lDQogICAgZ3JvdXBfYnkoV29ya2luZy5Jb24pICU+JQ0KICAgIHN1bW1hcmlzZShhY3Jvc3MoZXZlcnl0aGluZygpLCBtZWRpYW4pKQ0KDQpwcmV0dHlUYWJsZShpb25fbWVhbl9zdW0pDQpgYGANCg0KYGBge3IgaW9uX2Rpc3RyaWJ1dGlvbn0NCg0KZGF0YSAlPiUNCiAgZ3JvdXBfYnkoV29ya2luZy5Jb24pICU+JQ0KICBzdW1tYXJpc2UodG90YWxfaW9uID0gbigpKSAlPiUNCiAgZ2dwbG90KCkgKw0KICAgbGFicyh0aXRsZSA9IHBhc3RlKCJSb3prxYJhZCBnxYLDs3dueWNoIGpvbsOzdyBvZHBvd2lhZGFqxIVjeWNoIHphIHRyYW5zcG9ydCDFgmFkdW5rdSB3IGJhdGVyaWkiKSwNCiAgICAgICAgICAgeCA9ICJXb3JraW5nLklvbiIsDQogICAgICAgICAgIHkgPSAiQ291bnQiKSArDQogICAgICBnZW9tX2JhcihhZXMoeCA9IHJlb3JkZXIoV29ya2luZy5Jb24sIHRvdGFsX2lvbiksIHkgPSB0b3RhbF9pb24sIGZpbGwgPSBXb3JraW5nLklvbiksIHN0YXQgPSAiaWRlbnRpdHkiKSArDQogICAgICB0aGVtZV9taW5pbWFsKCkgDQpgYGANCk5hIHByemVkc3Rhd2lvbnltIHd5a3Jlc2llIHphcHJlemVudG93YW5vIHJvemvFgmFkIGfFgsOzd255Y2ggam9uw7N3IHXFvHl3YW55Y2ggZG8gdHJhbnNwb3J0dSDFgmFkdW5rdSB3IGJhdGVyaWFjaC4gV3lyYcW6bmllIGRvbWludWplIGxpdCAoTGkpLCBrdMOzcnkgd3lzdMSZcHVqZSB6bmFjem5pZSBjesSZxZtjaWVqIG5pxbwgaW5uZSBqb255IHRqLiBwcmF3aWUgMjUwMCByYXp5LiBTdWdlcnVqZSB0byBwb3dzemVjaG5lIHphc3Rvc293YW5pZSB0ZWNobm9sb2dpaSBvcGFydHljaCBuYSBsaXRvd28tam9ub3d5Y2ggcm96d2nEhXphbmlhY2gsIGNvIGplc3QgemdvZG5lIHogaWNoIHN6ZXJva2ltIHd5a29yenlzdGFuaWVtIHcgcHJ6ZW15xZtsZSBlbGVrdHJvbmlraSBpIG1hZ2F6eW5vd2FuaWEgZW5lcmdpaS4gDQpTemN6ZWfDs2xuxIUgemFsZXTEhSBiYXRlcmlpIGxpdG93by1qb25vd3ljaCBqZXN0ICB3eXNva8SFIGfEmXN0b8WbxIcgZW5lcmdpaS4gVG8gem5hY3p5LCDFvGUgbW9nxIUgbWFnYXp5bm93YcSHIGR1xbzEhSBpbG/Fm8SHIGVuZXJnaWkgcHJ6eSBuaWV3aWVsa2ljaCByb3ptaWFyYWNoIGkgbmlza2llaiB3YWR6ZSwgY28gY3p5bmkgamUgZG9za29uYcWCeW0gcm96d2nEhXphbmllbSBkbGEgcHJ6ZW5vxZtueWNoIHVyesSFZHplxYQsIHRha2ljaCBqYWsgbGFwdG9weSBpIHRhYmxldHkgW0Bwb3Jvd25hbmllLWxpdG93eWNoLWJhdGVyaWldLiBXIHplc3Rhd2llbml1IHBpZXJ3aWFzdGvDs3cgbGl0IHBsYXN1amUgc2nEmSBuYSBkcnVnaW0gbWllanNjdSBwb2Qgd3pnbMSZZGVtIGdyYXdpbWV0cmN6bmVqIGfEmXN0b8WbY2kgZW5lcmdpaSwgemFyYXogemEgd2FwbmllbS4gXA0KUG96b3N0YcWCZSBqb255LCB0YWtpZSBqYWsgd2FwxYQgKENhKSwgbWFnbmV6IChNZyksIGkgY3luayAoWm4pLCByw7N3bmllxbwgem5hamR1asSFIHphc3Rvc293YW5pZSwgYWxlIHcgem5hY3puaWUgbW5pZWpzenltIHpha3Jlc2llLiBPYmVjbm/Fm8SHIGpvbsOzdyB0YWtpY2ggamFrIHPDs2QgKE5hKSBpIHBvdGFzIChLKSBtb2fEhSB3c2thenl3YcSHIG5hIGJhZGFuaWEgbmFkIGFsdGVybmF0eXdhbWkgZGxhIGxpdHUsIGplZG5hayBpY2ggemFzdG9zb3dhbmllIGplc3Qgb2JlY25pZSBvZ3Jhbmljem9uZS4NCg0KLS0tLS0tLS0tLS0tDQoNClBvbmnFvGVqIHpuYWpkdWrEhSBzacSZIGludGVyYWt0eXduZSB3eWtyZXN5IHB1ZGXFgmtvd2UgZGxhIGthxbxkZWogem1pZW5uZWogbnVtZXJ5Y3puZWogdyB6YmlvcnplIGRhbnljaCB6IHBvZHppYcWCZW0gbmEgZ8WCw7N3bmUgam9ueS4gV3lrcmVzeSB0ZSB1bW/FvGxpd2lhasSFIGVrc3Bsb3JhY2rEmSByb3prxYJhZHUgd2FydG/Fm2NpLCBpZGVudHlmaWthY2rEmSBwb3RlbmNqYWxueWNoIHdhcnRvxZtjaSBvZHN0YWrEhWN5Y2ggb3JheiBwb3LDs3duYW5pZSB6bWllbm5vxZtjaSB3IGthxbxkZWogem1pZW5uZWogZGxhIHBvc3plZ8OzbG55Y2ggam9uw7N3Lg0KDQpgYGB7ciBpbnRlcmFjdGl2ZV9ib3hwbG90cywgcmVzdWx0cz0naGlkZScsIGZpZy5rZWVwID0gJ25vbmUnLCBmaWcud2lkdGg9MTR9DQpmb3IgKGF0dHJfbmFtZSBpbiBudW1lcmljYWxfYXR0cnMpIHsNCiAgcCA8LSBwbG90X2x5KA0KICAgIGRhdGEsIA0KICAgIHkgPSB+Z2V0KGF0dHJfbmFtZSksDQogICAgdHlwZSA9ICJib3giLA0KICAgIGJveHBvaW50cyA9ICJhbGwiLA0KICAgIGppdHRlciA9IDAuMywNCiAgICBwb2ludHBvcyA9IC0xLjgsDQogICAgaG92ZXJJbmZvID0gInRleHQiLA0KICAgIGNvbG9yID0gfldvcmtpbmcuSW9uDQogICkgJT4lDQogICAgbGF5b3V0KA0KICAgICAgdGl0bGUgPSBwYXN0ZSgiQm94cGxvdCBvZiIsIGF0dHJfbmFtZSksDQogICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSBhdHRyX25hbWUpDQogICAgKQ0KDQogIHByaW50KHApDQp9DQoNCmBgYA0KDQpXbmlvc2tpIHogYW5hbGl6eSB3eWtyZXPDs3cgcm96a8WCYWTDs3cgd2FydG/Fm2NpIHd5YnJhbnljaCBhdHJ5YnV0w7N3IHogcG9kemlhxYJlbSBuYSBnxYLDs3dueSBqb246DQoNCi0gRGxhIMWbcmVkbmllZ28gbmFwacSZY2lhIGxpdCBjaGFyYWt0ZXJ5enVqZSBzacSZIG5hanfEmcW8c3p5bSBwcnplZHppYcWCZW0gbmFqY3rEmXN0c3p5Y2ggd2FydG/Fm2NpLCBhbGUgamVkbm9jemXFm25pZSBqZXN0IGpvbmVtLCBkbGEga3TDs3JlZ28gbW/FvG5hIHppZGVudHlmaWtvd2HEhyBuYWp3acSZY2VqIHdhcnRvxZtjaSBvZHN0YWrEhWN5Y2guDQotIFcgcHJ6eXBhZGt1IHBvamVtbm/Fm2NpIGdyYXdpbWV0cnljem5laiBqb25hbWksIGt0w7NyZSBjaGFyYWt0ZXJ5enVqxIUgc2nEmSBuYWp3acSZa3N6xIUgbGljemLEhSBzaWxuaWUgb2RzdGFqxIVjeWNoIG9ic2Vyd2Fjamkgc8SFIG1hZ25leiAoTWcpIGkgZ2xpbiAoQWwpLg0KLSBOYSB0bGEgd3N6eXN0a2ljaCBjZWNoLCBjZXogd3lrYXp1amUgbmFqd2nEmWtzemUgc2t1cGllbmllIHdhcnRvxZtjaSwgdyBwb3LDs3duYW5pdSBkbyBpbm55Y2ggam9uw7N3IHBvc2lhZGEgbmFqbW5pZWogd2FydG/Fm2NpIG9kc3RhasSFY3ljaC4NCi0gTmFqd2nEmWNlaiB3YXJ0b8WbY2kgc2lsbmllIG9kc3RhasSFY3ljaCBtb8W8bmEgcHJ6eXBpc2HEhyBsaXRvd2ksIG1hZ25lem93aSwgaSBnbGlub3dpLiBMaWN6bmUgd2FydG/Fm2NpIG9kc3RhasSFY2UgdyBwcnp5cGFka3UgbGl0dSBtb2fEhSBiecSHIHNwb3dvd29kd2FuZSBkb21pbmFjasSFIHcgd3lrb3J6eXN0YW5pdSBuYWQgaW5ueW1pIGpvbmFtaS4NCi0gV2FydG8gemF1d2HFvHnEhywgxbxlIGxpdCwgbmFqY3rEmcWbY2llaiB3eWtvcnp5c3R5d2FueSBqYWtvIGpvbiBnxYLDs3dueSwgdyB3acSZa3N6b8WbY2kgY2VjaCBjaGFyYWt0ZXJ5enVqZSBzacSZIHLDs3dub21pZXJueW0gcm96a8WCYWRlbSwgY28gbW/FvGUgYnnEhyBpc3RvdG55bSBjenlubmlraWVtIGRlY3lkdWrEhWN5bSBvIGplZ28gZG9taW5hY2ppLiAgDQoNCiMjIyBXem9yeSBjaGVtaWN6bmUgbWF0ZXJpYcWCdSBiYXRlcmlpDQoNCmBgYHtyIGZvcm11bGEtY2hhcmdlLWRpc3RyaWJ1dGlvbn0NCmZvcm11bGFfY2hhcmdlX3N1bW1hcnkgPC0gZGF0YSAlPiUNCiAgZ3JvdXBfYnkoRm9ybXVsYS5DaGFyZ2UpICU+JQ0KICBzdW1tYXJpc2UodG90YWwgPSBuKCkpICU+JQ0KICBhcnJhbmdlKGRlc2ModG90YWwpKQ0KDQpwZXJjZW50aWxlXzk5IDwtIHF1YW50aWxlKHNlbGVjdChmb3JtdWxhX2NoYXJnZV9zdW1tYXJ5LCB0b3RhbCksIHByb2JzID0gMC45OSwgbmEucm0gPSBUUlVFKQ0KZm9ybXVsYV9jaGFyZ2VfdmFsdWVzXzk5X3BlcmNlbnRpbGUgPC0gZm9ybXVsYV9jaGFyZ2Vfc3VtbWFyeSAlPiUNCiAgZmlsdGVyKHRvdGFsID49IHBlcmNlbnRpbGVfOTkpDQoNCmthYmxlKGZvcm11bGFfY2hhcmdlX3ZhbHVlc185OV9wZXJjZW50aWxlKQ0KDQpmb3JtdWxhX2NoYXJnZV92YWx1ZXNfOTlfcGVyY2VudGlsZSAlPiUNCiAgZ2dwbG90KCkgKw0KICAgbGFicyh0aXRsZSA9IHBhc3RlKCJXesOzcnkgY2hlbWljem5lIG1hdGVyaWHFgnUgYmF0ZXJpaSB3IHN0YW5pZSBuYcWCYWRvd2FueW0iKSwNCiAgICAgICAgICAgeCA9ICJXesOzciB3IHN0YW5pZSBuYcWCYWRvd2FueW0iLA0KICAgICAgICAgICB5ID0gIkxpY3piYSBvYnNlcndhY2ppIikgKw0KICAgICAgZ2VvbV9iYXIoYWVzKHggPSByZW9yZGVyKEZvcm11bGEuQ2hhcmdlLCB0b3RhbCksIHkgPSB0b3RhbCwgZmlsbCA9IEZvcm11bGEuQ2hhcmdlKSwgc3RhdCA9ICJpZGVudGl0eSIsICB3aWR0aCA9IDAuNSkgKw0KICAgICAgdGhlbWUoDQogICAgICAgIHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSwNCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxLCBzaXplID0gOCkNCiAgICAgICkNCmBgYA0KDQpXeWtyZXMgcHJ6ZWRzdGF3aWEgbGljemLEmSB3eXN0xIVwaWXFhCByw7PFvG55Y2ggd3pvcsOzdyBjaGVtaWN6bnljaCBtYXRlcmlhxYLDs3cgYmF0ZXJpaSB3IHN0YW5pZSBuYcWCYWRvd2FueW0uIFcgemJpb3J6ZSBqZXN0IGByIGxlbmd0aCh1bmlxdWUoZGF0YSRGb3JtdWxhLkNoYXJnZSkpYCByw7PFvG55Y2ggd3pvcsOzdyBjaGVtaWN6bnljaCBtYXRlcmlhxYLDs3cgYmF0ZXJpaSB3IHN0YW5pZSBuYcWCYWRvd2FueW0uIE5hamxpY3puaWVqc3p5bWkgc8SFIE1uTzIsIFRpTzIsIFZvMiwgQ3JPMiwgTmlPMiwgRmVPMi4NCg0KLS0tLS0tLS0tLS0tLS0NCg0KYGBge3IgZm9ybXVsYS1kaXNjaGFyZ2Utc3VtbWFyeSwgZmlnLndpZHRoPTE2LCBmaWcuaGVpZ2h0PTh9DQoNCmZvcm11bGFfZGlzY2hhcmdlX3N1bW1hcnkgPC0gZGF0YSAlPiUNCiAgZ3JvdXBfYnkoRm9ybXVsYS5EaXNjaGFyZ2UpICU+JQ0KICBzdW1tYXJpc2UodG90YWwgPSBuKCkpICU+JQ0KICBhcnJhbmdlKGRlc2ModG90YWwpKQ0KDQpkaXNjaGFyZ2VfcGVyY2VudGlsZV85OSA8LSBxdWFudGlsZShzZWxlY3QoZm9ybXVsYV9kaXNjaGFyZ2Vfc3VtbWFyeSwgdG90YWwpLCBwcm9icyA9IDAuOTksIG5hLnJtID0gVFJVRSkNCmZvcm11bGFfZGlzY2hhcmdlX3ZhbHVlc185OV9wZXJjZW50aWxlIDwtIGZvcm11bGFfZGlzY2hhcmdlX3N1bW1hcnkgJT4lDQogIGZpbHRlcih0b3RhbCA+PSBkaXNjaGFyZ2VfcGVyY2VudGlsZV85OSkNCg0KZm9ybXVsYV9kaXNjaGFyZ2VfdmFsdWVzXzk5X3BlcmNlbnRpbGUNCg0KZm9ybXVsYV9kaXNjaGFyZ2VfdmFsdWVzXzk5X3BlcmNlbnRpbGUgJT4lDQogIGdncGxvdCgpICsNCiAgIGxhYnModGl0bGUgPSBwYXN0ZSgiV3rDs3J5IGNoZW1pY3puZSBtYXRlcmlhxYJ1IGJhdGVyaWkgdyBzdGFuaWUgcm96xYJhZG93YW55bSIpLA0KICAgICAgICAgICB4ID0gIld6w7NyIHcgc3RhbmllIHJvesWCYWRvd2FueW0iLA0KICAgICAgICAgICB5ID0gIkxpY3piYSBvc2Vyd2FjamkiKSArDQogICAgICBnZW9tX2JhcihhZXMoeCA9IHJlb3JkZXIoRm9ybXVsYS5EaXNjaGFyZ2UsIHRvdGFsKSwgeSA9IHRvdGFsLCBmaWxsID0gRm9ybXVsYS5EaXNjaGFyZ2UpLCBzdGF0ID0gImlkZW50aXR5IiwgIHdpZHRoID0gMC41KSArDQogICAgICB0aGVtZSgNCiAgICAgICAgdGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEsIHNpemUgPSA4KSwNCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMzApDQogICAgICApDQoNCmBgYA0KDQpXeWtyZXMgcHJ6ZWRzdGF3aWEgbGljemLEmSB3eXN0xIVwaWXFhCByw7PFvG55Y2ggd3pvcsOzdyBjaGVtaWN6bnljaCBtYXRlcmlhxYLDs3cgYmF0ZXJpaSB3IHN0YW5pZSByb3rFgmFkb3dhbnltLiBXIHpiaW9yemUgamVzdCBgciBsZW5ndGgodW5pcXVlKGRhdGEkRm9ybXVsYS5EaXNjaGFyZ2UpKWAgcsOzxbxueWNoIHd6b3LDs3cgY2hlbWljem55Y2ggbWF0ZXJpYcWCw7N3IGJhdGVyaWkgdyBzdGFuaWUgcm96xYJhZG93YW55bS4gTmFqY3rEmcWbY2llaiB3eXN0xJlwdWrEhWNlIHd6b3J5IHRvIExpVk9GMTEsIExpMk81RjUsIExpRmVQTzQsIExpQ29QTzQuIFpuYWN6bmEgd2nEmWtzem/Fm8SHIG5hamN6xJnFm2NpZWogd3lzdMSZcHVqxIVjeWNoIHd6b3LDs3cgemF3aWVyYSBjesSFc3RlY3prxJkgbGl0dS4NCg0KDQojIyBBbmFsaXphIHfFgmHFm2Npd2/Fm2NpDQoNCiMjIyBTdGFiaWxub8WbxIcgdyBzdGFuaWUgbmHFgmFkb3dhbnltIGkgcm96xYJhZG93YW55bQ0KDQoqKlN0YWJpbGl0eSBDaGFyZ2UqKg0KDQotIE9rcmXFm2xhIHN0YWJpbG5vxZvEhyBtYXRlcmlhxYJ1IHcgYmF0ZXJpaSwgZ2R5IGplc3QgdyBwZcWCbmkgbmHFgmFkb3dhbnkNCi0gV3nFvHN6YSBzdGFiaWxub8WbxIcgb3puYWN6YSwgxbxlIG1hdGVyaWHFgiBixJlkemllIG1uaWVqIHBvZGF0bnkgbmEgdXN6a29kemVuaWEgbHViIGRlZ3JhZGFjasSZIHBvZGN6YXMgxYJhZG93YW5pYS4gSmVzdCB0byBpc3RvdG5lLCBhYnkgemFwZXduacSHIGTFgnVnb3Ryd2HFgmUgZHppYcWCYW5pZSBiYXRlcmlpIGJleiB1dHJhdHkgamVqIHfFgmHFm2Npd2/Fm2NpLg0KDQoqKlN0YWJpbGl0eSBEaXNjaGFyZ2UqKg0KDQotIE9rcmXFm2xhIHN0YWJpbG5vxZvEhyBtYXRlcmlhxYJ1IHcgYmF0ZXJpaSwgZ2R5IGplc3QgdyBwZcWCbmkgcm96xYJhZG93YW55Lg0KLSBTdGFiaWxub8WbxIcgdyBzdGFuaWUgcm96xYJhZG93YW55bSBqZXN0IGtsdWN6b3dhIGRsYSB1dHJ6eW1hbmlhIGVmZWt0eXdub8WbY2kgYmF0ZXJpaSBwcnpleiB3aWVsZSBjeWtsaSDFgmFkb3dhbmlhIGkgcm96xYJhZG93YW5pYS4gWmFwZXduaWEgb25hLCDFvGUgbWF0ZXJpYcWCIG5pZSB1bGVnbmllIGRlZ3JhZGFjamksIGNvIG1vZ8WCb2J5IHByb3dhZHppxIcgZG8gem1uaWVqc3plbmlhIHBvamVtbm/Fm2NpIGkgxbx5d290bm/Fm2NpIGJhdGVyaWkuDQoNCg0KYGBge3J9DQpzdW1tYXJ5X3N0YWJpbGl0eV9jaGFyZ2Vfd29ya2luZ19pb24gPC0gZGF0YSAlPiUNCiAgZ3JvdXBfYnkoV29ya2luZy5Jb24pICU+JQ0KICBzdW1tYXJpc2Uoc3RhYmlsaXR5X2NoYXJnZV9tZWFuID0gbWVhbihTdGFiaWxpdHkuQ2hhcmdlKSwNCiAgICAgICAgICAgIHN0YWJpbGl0eV9jaGFyZ2VfbWVkaWFuID0gbWVkaWFuKFN0YWJpbGl0eS5DaGFyZ2UpLA0KICAgICAgICAgICAgc3RhYmlsaXR5X2NoYXJnZV9taW4gPSBtaW4oU3RhYmlsaXR5LkNoYXJnZSksDQogICAgICAgICAgICBzdGFiaWxpdHlfY2hhcmdlX21heCA9IG1heChTdGFiaWxpdHkuQ2hhcmdlKSwNCiAgICAgICAgICAgIHRvdGFsID0gbigpKSAlPiUNCiAgYXJyYW5nZShkZXNjKHN0YWJpbGl0eV9jaGFyZ2VfbWVkaWFuKSkNCg0KcF9zdGFiaWxpdHlfY2hhcmdlX3dvcmtpbmdfaW9uIDwtIGdncGxvdChzdW1tYXJ5X3N0YWJpbGl0eV9jaGFyZ2Vfd29ya2luZ19pb24sIGFlcyh4ID0gdG90YWwsIHkgPSBzdGFiaWxpdHlfY2hhcmdlX21lZGlhbiwgY29sb3IgPSBXb3JraW5nLklvbikpICsNCiAgZ2VvbV9wb2ludChhZXMoDQogICAgdGV4dCA9IHBhc3RlKA0KICAgICAgIldvcmtpbmcgSW9uOiIsIFdvcmtpbmcuSW9uLA0KICAgICAgIjxicj5NZWRpYW4gOiIsIHJvdW5kKHN0YWJpbGl0eV9jaGFyZ2VfbWVkaWFuLCAyKSwNCiAgICAgICI8YnI+TWVhbiA6Iiwgcm91bmQoc3RhYmlsaXR5X2NoYXJnZV9tZWFuLCAyKSwNCiAgICAgICI8YnI+TWluIDoiLCByb3VuZChzdGFiaWxpdHlfY2hhcmdlX21pbiwgMiksDQogICAgICAiPGJyPk1heCA6Iiwgcm91bmQoc3RhYmlsaXR5X2NoYXJnZV9tYXgsIDIpDQogICAgKQ0KICApLCAgc2l6ZSA9IDMpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJTdGFiaWxub8WbxIcgdyBzdGFuaWUgbmHFgmFkb3dhbnltIGEgZ8WCw7N3bnkgam9uIiwNCiAgICB4ID0gIkxpY3piYSBvYnNlcndhY2ppIiwNCiAgICB5ID0gIldza2HFvG5payBzdGFiaWxub8WbY2kgdyBzdGFuaWUgbmHFgmFkb3dhbnltIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCmdncGxvdGx5KHBfc3RhYmlsaXR5X2NoYXJnZV93b3JraW5nX2lvbiwgdG9vbHRpcCA9ICJ0ZXh0IikNCg0Kc3VtbWFyeV9zdGFiaWxpdHlfZGlzY2hhcmdlX3dvcmtpbmdfaW9uIDwtIGRhdGEgJT4lDQogIGdyb3VwX2J5KFdvcmtpbmcuSW9uKSAlPiUNCiAgc3VtbWFyaXNlKHN0YWJpbGl0eV9kaXNjaGFyZ2VfbWVhbiA9IG1lYW4oU3RhYmlsaXR5LkRpc2NoYXJnZSksDQogICAgICAgICAgICBzdGFiaWxpdHlfZGlzY2hhcmdlX21lZGlhbiA9IG1lZGlhbihTdGFiaWxpdHkuRGlzY2hhcmdlKSwNCiAgICAgICAgICAgIHN0YWJpbGl0eV9kaXNjaGFyZ2VfbWluID0gbWluKFN0YWJpbGl0eS5EaXNjaGFyZ2UpLA0KICAgICAgICAgICAgc3RhYmlsaXR5X2Rpc2NoYXJnZV9tYXggPSBtYXgoU3RhYmlsaXR5LkRpc2NoYXJnZSksDQogICAgICAgICAgICB0b3RhbCA9IG4oKSkgJT4lDQogIGFycmFuZ2UoZGVzYyhzdGFiaWxpdHlfZGlzY2hhcmdlX21lZGlhbikpDQoNCnBfc3RhYmlsaXR5X2Rpc2NoYXJnZV93b3JraW5nX2lvbiA8LSBnZ3Bsb3Qoc3VtbWFyeV9zdGFiaWxpdHlfZGlzY2hhcmdlX3dvcmtpbmdfaW9uLCBhZXMoeCA9IHRvdGFsLCB5ID0gc3RhYmlsaXR5X2Rpc2NoYXJnZV9tZWRpYW4sIGNvbG9yID0gV29ya2luZy5Jb24pKSArDQogIGdlb21fcG9pbnQoDQogICAgYWVzKA0KICAgIHRleHQgPSBwYXN0ZSgNCiAgICAgICJXb3JraW5nIElvbjoiLCBXb3JraW5nLklvbiwNCiAgICAgICI8YnI+TWVkaWFuIDoiLCByb3VuZChzdGFiaWxpdHlfZGlzY2hhcmdlX21lZGlhbiwgMiksDQogICAgICAiPGJyPk1lYW4gOiIsIHJvdW5kKHN0YWJpbGl0eV9kaXNjaGFyZ2VfbWVhbiwgMiksDQogICAgICAiPGJyPk1pbiA6Iiwgcm91bmQoc3RhYmlsaXR5X2Rpc2NoYXJnZV9taW4sIDIpLA0KICAgICAgIjxicj5NYXggOiIsIHJvdW5kKHN0YWJpbGl0eV9kaXNjaGFyZ2VfbWF4LCAyKQ0KICAgICkNCiAgKSwgc2l6ZSA9IDMpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJTdGFiaWxub8WbxIcgdyBzdGFuaWUgcm96xYJhZG93YW55bSBhIGfFgsOzd255IGpvbiIsDQogICAgeCA9ICJMaWN6YmEgb2JzZXJ3YWNqaSIsDQogICAgeSA9ICJXc2thxbxuaWsgc3RhYmlsbm/Fm2NpIHcgc3RhbmllIHJvesWCYWRvd2FueW0iDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KZ2dwbG90bHkocF9zdGFiaWxpdHlfZGlzY2hhcmdlX3dvcmtpbmdfaW9uLCB0b29sdGlwID0gInRleHQiKQ0KYGBgDQoNCioqU3RhYmlsbm/Fm8SHIHcgc3RhbmllIG5hxYJhZG93YW55bTogKioNCg0KICAtIFdhcnRvxZtjaSB3c2thxbpuaWthIHN0YWJpbG5vxZtjaSBtYXRlcmlhxYLDs3cgdyB3acSZa3N6b8WbY2kgbWllc3pjesSFIHNpxJkgcG9uacW8ZWogMCwxNSANCiAgLSBKb255IHd5a2F6dWrEhWNlIG5hand5xbxzesSFIHN0YWJpbG5vxZvEhyB3IHN0YW5pZSBuYcWCYWRvd2FueW0gdG8gWSwgQWwsIFpuLCBDYSBpIE1nIA0KICAtIEpvbnkgd3lrYXp1asSFY2UgbmFqbmnFvHN6xIUgc3RhYmlsbm/Fm8SHIHcgc3RhbmllIG5hxYJhZG93YW55bSB0byANCg0KKipTdGFiaWxub8WbxIcgdyBzdGFuaWUgcm96xYJhZG93YW55bTogKioNCg0KLSBXYXJ0b8WbY2kgd3NrYcW6bmlrYSBzdGFiaWxub8WbY2kgbWF0ZXJpYcWCw7N3IHcgd2nEmWtzem/Fm2NpIG1pZXN6Y3rEhSBzacSZIHBvbmnFvGVqIDAsMTAgDQotIEpvbnkgd3lrYXp1asSFY2UgbmFqd3nFvHN6xIUgc3RhYmlsbm/Fm8SHIHcgc3RhbmllIG5hxYJhZG93YW55bSB0byBZLCBNZywgWm4gb3JheiBBbCANCg0KU3pjemVnw7NsbsSFIHV3YWfEmSBwcnp5Y2nEhWdhIGxpdCwga3TDs3J5IGNoYXJha3Rlcnl6dWplIHNpxJkgbmFqd2nEmWtzesSFIGxpY3pixIUgb2JzZXJ3YWNqaSBpIGplZG5vY3plxZtuaWUgemFqbXVqZSBwb8WbcmVkbmllIG1pZWpzY2UgdyB6ZXN0YXdpZW5pdSBwb2Qgd3pnbMSZZGVtIHdhcnRvxZtjaSB3c2thxbpuaWvDs3cgc3RhYmlsbm/Fm2NpLg0KDQojIyMgWm1pYW5hIG9iasSZdG/Fm2NpIGRsYSBkYW5lZ28ga3Jva3UgbmFwacSZY2lhIHogcG9kemlhxYJlbSBuYSBqb255IGfFgsOzd25lDQoNCmBgYHtyIHN1bW1hcnktbWF4LWRhdGEtdm9sdW1lLXdvcmtpbmctaW9uLCBmaWcud2lkdGg9MTR9DQoNCnN1bW1hcnlfbWF4X2RhdGFfdm9sdW1lX3dvcmtpbmdfaW9uIDwtIGRhdGEgJT4lDQogIGdyb3VwX2J5KFdvcmtpbmcuSW9uKSAlPiUNCiAgc3VtbWFyaXNlKG1heF9kYXRhX3ZvbHVtZV9tZWFuID0gbWVhbihNYXguRGVsdGEuVm9sdW1lKSwNCiAgICAgICAgICAgIG1heF9kYXRhX3ZvbHVtZV9tZWRpYW4gPSBtZWRpYW4oTWF4LkRlbHRhLlZvbHVtZSksDQogICAgICAgICAgICBtYXhfZGF0YV92b2x1bWVfbWluID0gbWluKE1heC5EZWx0YS5Wb2x1bWUpLA0KICAgICAgICAgICAgbWF4X2RhdGFfdm9sdW1lX21heCA9IG1heChNYXguRGVsdGEuVm9sdW1lKSwNCiAgICAgICAgICAgIHRvdGFsID0gbigpKSAlPiUNCiAgYXJyYW5nZShtYXhfZGF0YV92b2x1bWVfbWVhbikNCg0KcHJldHR5VGFibGUoc3VtbWFyeV9tYXhfZGF0YV92b2x1bWVfd29ya2luZ19pb24pDQoNCnAgPC0gZ2dwbG90KHN1bW1hcnlfbWF4X2RhdGFfdm9sdW1lX3dvcmtpbmdfaW9uLCBhZXMoeCA9IHRvdGFsLCB5ID0gbWF4X2RhdGFfdm9sdW1lX21lZGlhbiwgY29sb3IgPSBXb3JraW5nLklvbikpICsNCiAgZ2VvbV9wb2ludChhZXMoDQogICAgdGV4dCA9IHBhc3RlKA0KICAgICAgIldvcmtpbmcgSW9uOiIsIFdvcmtpbmcuSW9uLA0KICAgICAgIjxicj5NZWRpYW4gOiIsIHJvdW5kKG1heF9kYXRhX3ZvbHVtZV9tZWRpYW4sIDIpLA0KICAgICAgIjxicj5NZWFuIDoiLCByb3VuZChtYXhfZGF0YV92b2x1bWVfbWVhbiwgMiksDQogICAgICAiPGJyPk1pbiA6Iiwgcm91bmQobWF4X2RhdGFfdm9sdW1lX21pbiwgMiksDQogICAgICAiPGJyPk1heCA6Iiwgcm91bmQobWF4X2RhdGFfdm9sdW1lX21heCwgMikNCiAgICApDQogICksIHNpemUgPSAzLCBhbHBoYSA9IDAuNykgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk1lZGlhbmEgem1pYW55IG9iasSZdG/Fm2NpIHcgJSBkbGEgZGFuZWdvIGtyb2t1IG5hcGnEmWNpYSBkbGEgam9uw7N3IGfFgsOzd255Y2giLA0KICAgIHggPSAiTGljemJhIG9ic2Vyd2FjamkiLA0KICAgIHkgPSAiTWVkaWFuYSBtYWtzeW1hbG5laiB6bWlhbnkgb2JqxJl0b8WbY2kiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCg0KZ2dwbG90bHkocCwgdG9vbHRpcCA9ICJ0ZXh0IikNCmBgYA0KDQpOYWpuacW8c3rEhSB6bWlhbsSFIG5hcGnEmWNpYSBjaGFyYWt0ZXJ5enVqZSBzacSZIGxpdCAoTGkpLCBhIG5hand5xbxzesSFIGl0ciAoWSkuDQoNCg0KIyMjIFBvamVtbm/Fm8SHIGkgZ8SZc3RvxZvEhyBncmF3aW1ldHJ5Y3puYQ0KDQoqKkfEmXN0b8WbxIcgR3Jhd2ltZXRyeWN6bmEqKg0KDQotIFdza2F6dWplIGlsZSBlbmVyZ2lpIGRvc3TEmXBuZWogamVzdCB3IGJhdGVyaWkgdyBvZG5pZXNpZW5pdSBkbyBqZWogbWFzeSAoV2gva2cpLg0KLSBHxJlzdG/Fm8SHIGdyYXdpbWV0cnljem5hIGluZm9ybXVqZSwgamFrIGVmZWt0eXduaWUgYmF0ZXJpYSBtYWdhenludWplIGVuZXJnacSZIHcgc3Rvc3Vua3UgZG8gc3dvamVqIHdhZ2kuIEplc3Qga2x1Y3pvd2EgZGxhIGFwbGlrYWNqaSwgZ2R6aWUgd2HFvG5hIGplc3QgemFyw7N3bm8gcG9qZW1ub8WbxIcgZW5lcmdldHljem5hLCBqYWsgaSBsZWtrb8WbxIcsIG5wLiB3IHNhbW9jaG9kYWNoIGVsZWt0cnljem55Y2ggY3p5IGRyb25hY2guDQoNCmBgYHtyIHN1bW1hcnktZ3JhdmltZXRyaWMtZW5lcmd5LXdvcmtpbmctaW9uLCBmaWcud2lkdGg9MTR9DQoNCnN1bW1hcnlfZ3JhdmltZXRyaWNfZW5lcmd5X3dvcmtpbmdfaW9uIDwtIGRhdGEgJT4lDQogIGdyb3VwX2J5KFdvcmtpbmcuSW9uKSAlPiUNCiAgc3VtbWFyaXNlKGdyYXZpbWV0cmljX2VuZXJneV9tZWFuID0gbWVhbihHcmF2aW1ldHJpYy5FbmVyZ3kpLA0KICAgICAgICAgICAgZ3JhdmltZXRyaWNfZW5lcmd5X21lZGlhbiA9IG1lZGlhbihHcmF2aW1ldHJpYy5FbmVyZ3kpLCwNCiAgICAgICAgICAgIGdyYXZpbWV0cmljX2VuZXJneV9taW4gPSBtaW4oR3JhdmltZXRyaWMuRW5lcmd5KSwNCiAgICAgICAgICAgIGdyYXZpbWV0cmljX2VuZXJneV9tYXggPSBtYXgoR3JhdmltZXRyaWMuRW5lcmd5KSwNCiAgICAgICAgICAgIHRvdGFsID0gbigpKSAlPiUNCiAgYXJyYW5nZShkZXNjKGdyYXZpbWV0cmljX2VuZXJneV9tZWRpYW4pKQ0KDQpwcmV0dHlUYWJsZShzdW1tYXJ5X2dyYXZpbWV0cmljX2VuZXJneV93b3JraW5nX2lvbikNCg0KcCA8LSBnZ3Bsb3Qoc3VtbWFyeV9ncmF2aW1ldHJpY19lbmVyZ3lfd29ya2luZ19pb24sIGFlcyh4ID0gdG90YWwsIHkgPSBncmF2aW1ldHJpY19lbmVyZ3lfbWVkaWFuLCBjb2xvciA9IFdvcmtpbmcuSW9uKSkgKw0KICBnZW9tX3BvaW50KGFlcygNCiAgICB0ZXh0ID0gcGFzdGUoDQogICAgICAiV29ya2luZyBJb246IiwgV29ya2luZy5Jb24sDQogICAgICAiPGJyPk1lZGlhbiA6Iiwgcm91bmQoZ3JhdmltZXRyaWNfZW5lcmd5X21lZGlhbiwgMiksDQogICAgICAiPGJyPk1lYW4gOiIsIHJvdW5kKGdyYXZpbWV0cmljX2VuZXJneV9tZWFuLCAyKSwNCiAgICAgICI8YnI+TWluIDoiLCByb3VuZChncmF2aW1ldHJpY19lbmVyZ3lfbWluLCAyKSwNCiAgICAgICI8YnI+TWF4IDoiLCByb3VuZChncmF2aW1ldHJpY19lbmVyZ3lfbWF4LCAyKQ0KICAgICkNCiAgKSwgc2l6ZSA9IDMsIGFscGhhID0gMC43KSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTWVkaWFuYSBlbmVyZ2lpIGdyYXdpbWV0cnljem5laiBkbGEgam9uw7N3IGfFgsOzd255Y2giLA0KICAgIHggPSAiTGljemJhIG9ic2Vyd2FjamkiLA0KICAgIHkgPSAiTWVkaWFuYSBlbmVyZ2lpIGdyYXdpbWV0cnljem5laiINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQ0KDQpnZ3Bsb3RseShwLCB0b29sdGlwID0gInRleHQiKQ0KDQoNCmBgYA0KDQotIE5handpxJlrc3rEhSBnxJlzdG/Fm2NpxIUgZ3Jhd2ltZXRyeWN6bsSFIGNoYXJha3Rlcnl6dWrEhSBzacSZIG1hdGVyaWHFgnkgQ2EsIExpLCBZLCBOYSwgTWcNCi0gTmFqbW5pZWpzesSFIGfEmXN0b8WbY2nEhSBncmF3aW1ldHJ5Y3puxIUgY2hhcmFrdGVyeXp1asSFIHNpxJkgbWF0ZXJpYcWCeSBDcywgWm4sIFJiDQoNCioqUG9qZW1ub8WbxIcgR3Jhd2ltZXRyeWN6bmEqKg0KDQotIFdza2F6dWplIGlsZSBlbmVyZ2lpIGVsZWt0cnljem5laiBiYXRlcmlhIG1vxbxlIHByemVjaG93YcSHIHcgcHJ6ZWxpY3plbml1IG5hIGplZG5vc3RrxJkgbWFzeSAobUFoL2cpLg0KLSBJbSB3ecW8c3phIHBvamVtbm/Fm8SHIGdyYXdpbWV0cnljem5hLCB0eW0gd2nEmWNlaiBlbmVyZ2lpIGJhdGVyaWEgbW/FvGUgcHJ6ZWNob3d5d2HEhyBwcnp5IHRlaiBzYW1laiBtYXNpZS4gSmVzdCB0byBpc3RvdG5lIGRsYSB1cnrEhWR6ZcWEIHByemVub8WbbnljaCwgZ2R6aWUgemFsZcW8eSBuYW0gbmEgbWFrc3ltYWxpemFjamkgZW5lcmdpaSBwcnp5IG5pZXdpZWxraWVqIG1hc2llLg0KDQpgYGB7ciBzdW1tYXJ5LWdyYXZpbWV0cmljLWNhcGFjaXR5LXdvcmtpbmctaW9uLCBmaWcud2lkdGg9MTR9DQpzdW1tYXJ5X2dyYXZpbWV0cmljX2NhcGFjaXR5X3dvcmtpbmdfaW9uIDwtIGRhdGEgJT4lDQogIGdyb3VwX2J5KFdvcmtpbmcuSW9uKSAlPiUNCiAgc3VtbWFyaXNlKGdyYXZpbWV0cmljX2NhcGFjaXR5X21lYW4gPSBtZWFuKEdyYXZpbWV0cmljLkNhcGFjaXR5KSwNCiAgICAgICAgICAgIGdyYXZpbWV0cmljX2NhcGFjaXR5X21lZGlhbiA9IG1lZGlhbihHcmF2aW1ldHJpYy5DYXBhY2l0eSksDQogICAgICAgICAgICBncmF2aW1ldHJpY19jYXBhY2l0eV9taW4gPSBtaW4oR3JhdmltZXRyaWMuQ2FwYWNpdHkpLA0KICAgICAgICAgICAgZ3JhdmltZXRyaWNfY2FwYWNpdHlfbWF4ID0gbWF4KEdyYXZpbWV0cmljLkNhcGFjaXR5KSwNCiAgICAgICAgICAgIHRvdGFsID0gbigpKSAlPiUNCiAgYXJyYW5nZShkZXNjKGdyYXZpbWV0cmljX2NhcGFjaXR5X21lZGlhbikpDQoNCnByZXR0eVRhYmxlKHN1bW1hcnlfZ3JhdmltZXRyaWNfY2FwYWNpdHlfd29ya2luZ19pb24pDQoNCnAgPC0gZ2dwbG90KHN1bW1hcnlfZ3JhdmltZXRyaWNfY2FwYWNpdHlfd29ya2luZ19pb24sIGFlcyh4ID0gdG90YWwsIHkgPSBncmF2aW1ldHJpY19jYXBhY2l0eV9tZWRpYW4sIGNvbG9yID0gV29ya2luZy5Jb24pKSArDQogIGdlb21fcG9pbnQoYWVzKA0KICAgIHRleHQgPSBwYXN0ZSgNCiAgICAgICJXb3JraW5nIElvbjoiLCBXb3JraW5nLklvbiwNCiAgICAgICI8YnI+TWVkaWFuIDoiLCByb3VuZChncmF2aW1ldHJpY19jYXBhY2l0eV9tZWRpYW4sIDIpLA0KICAgICAgIjxicj5NZWFuIDoiLCByb3VuZChncmF2aW1ldHJpY19jYXBhY2l0eV9tZWFuLCAyKSwNCiAgICAgICI8YnI+TWluIDoiLCByb3VuZChncmF2aW1ldHJpY19jYXBhY2l0eV9taW4sIDIpLA0KICAgICAgIjxicj5NYXggOiIsIHJvdW5kKGdyYXZpbWV0cmljX2NhcGFjaXR5X21heCwgMikNCiAgICApDQogICksIHNpemUgPSAzLCBhbHBoYSA9IDAuNykgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk1lZGlhbmEgcG9qZW1ub8WbY2kgZ3Jhd2ltZXRyeWN6bmVqIGRsYSBnxYLDs3dueWNoIGpvbsOzdyIsDQogICAgeCA9ICJMaWN6YmEgb2JzZXJ3YWNqaSIsDQogICAgeSA9ICJNZWRpYW5hIHBvamVtbm/Fm2NpIGdyYXdpbWV0cnljem5laiINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQ0KDQpnZ3Bsb3RseShwLCB0b29sdGlwID0gInRleHQiKQ0KDQpgYGANCg0KLSBOYWp3acSZa3N6xIUgcG9qZW1ub8WbY2nEhSBncmF3aW1ldHJ5Y3puxIUgY2hhcmFrdGVyeXp1asSFIHNpxJkgbWF0ZXJpYcWCeSBBbCwgWSwgTWcsIENhLCBabg0KLSBOYWptbmllanN6xIUgcG9qZW1ub8WbY2nEhSBncmF3aW1ldHJ5Y3puxIUgY2hhcmFrdGVyeXp1asSFIHNpxJkgbWF0ZXJpYcWCeSBDcywgUmINCg0KWmFyw7N3bm8gcnViaWQgKFJiKSBqYWsgaSBjZXogKENzKSwga3TDs3JlIHd5cGFkYWrEhSBuYWpnb3J6ZWogcG9kIHd6Z2zEmWRlbSBlbmVyZ2lpIGkgZ2VzdG/Fm2NpIGdyYXdpbWV0cnljem5laiBzxIUgbmFqcnphZHppZWogd3lrb3J6eXN0eXdhbnltaSBqb25hbWkgZ8WCw7N3bnltaS4NCg0KLS0tLS0tLS0tLS0NCg0KUG9uacW8c3p5IHd5a3JlcyBwcnplZHN0YXdpYSB6YWxlxbxub8WbxIcgbWnEmWR6eSBsaWN6YsSFIG9ic2Vyd2FjamkgZGxhIG5hamN6xJnFm2NpZWogd3lzdMSZcHVqxIVjeWNoIFd6b3LDs3cgY2hlbWljem55Y2ggbWF0ZXJpYcWCw7N3IGJhdGVyaWkgdyBzdGFuaWUgbmHFgmFkb3dhbnltIGEgbWVkaWFuxIUgc3RhYmlsbm/Fm2NpIHcgc3RhbmllIG5hxYJhZG93YW55bS4gRGxhIGthxbxkZWdvIHd6b3J1IG9ibGljem9ubyBtaWFyeSBzdGF0eWN6bmUsIGt0w7NyZSB3ecWbd2lldGxhasSFIHNpxJkgcG8gbmFqZWNoYW5pdSBuYSBwdW5rdC4NCg0KYGBge3IgY2hhcmdlLXN0YWJpbGl0eS1mb3JtYXVsYS1jaGFyZ2Utc3VtbWFyeSwgZmlnLndpZHRoPTE0fQ0KDQpmb3JtdWxhX2NoYXJnZV9zdW1tYXJ5IDwtIGRhdGEgJT4lDQogIGdyb3VwX2J5KEZvcm11bGEuQ2hhcmdlKSAlPiUNCiAgc3VtbWFyaXNlKHRvdGFsID0gbigpKSAlPiUNCiAgYXJyYW5nZShkZXNjKHRvdGFsKSkNCg0KcGVyY2VudGlsZV85OSA8LSBxdWFudGlsZShzZWxlY3QoZm9ybXVsYV9jaGFyZ2Vfc3VtbWFyeSwgdG90YWwpLCBwcm9icyA9IDAuOTksIG5hLnJtID0gVFJVRSkNCmZvcm11bGFfY2hhcmdlX3ZhbHVlc185OV9wZXJjZW50aWxlIDwtIGZvcm11bGFfY2hhcmdlX3N1bW1hcnkgJT4lDQogIGZpbHRlcih0b3RhbCA+PSBwZXJjZW50aWxlXzk5KQ0KDQpzdW1tYXJ5X2RhdGEgPC0gZGF0YSAlPiUNCiAgZmlsdGVyKEZvcm11bGEuQ2hhcmdlICVpbiUgZm9ybXVsYV9jaGFyZ2VfdmFsdWVzXzk5X3BlcmNlbnRpbGUkRm9ybXVsYS5DaGFyZ2UpICU+JQ0KICBncm91cF9ieShGb3JtdWxhLkNoYXJnZSkgJT4lDQogIHN1bW1hcmlzZSh0b3RhbCA9IG4oKSwNCiAgICBzdGFiaWxpdHlfY2hhcmdlX21lYW4gPSBtZWFuKFN0YWJpbGl0eS5DaGFyZ2UpLA0KICAgICAgICAgICAgc3RhYmlsaXR5X2NoYXJnZV9tZWRpYW4gPSBtZWRpYW4oU3RhYmlsaXR5LkNoYXJnZSksDQogICAgICAgICAgICBzdGFiaWxpdHlfY2hhcmdlX21pbiA9IG1pbihTdGFiaWxpdHkuQ2hhcmdlKSwNCiAgICAgICAgICAgIHN0YWJpbGl0eV9jaGFyZ2VfbWF4ID0gbWF4KFN0YWJpbGl0eS5DaGFyZ2UpKSAlPiUNCiAgYXJyYW5nZShkZXNjKHRvdGFsKSkNCg0KcCA8LSBnZ3Bsb3Qoc3VtbWFyeV9kYXRhLCBhZXMoeCA9IHRvdGFsLCB5ID0gc3RhYmlsaXR5X2NoYXJnZV9tZWRpYW4pKSArDQogIGdlb21fcG9pbnQoYWVzKA0KICAgIHRleHQgPSBwYXN0ZSgNCiAgICAgICJGb3JtdWxhIGNoYXJnZToiLCBGb3JtdWxhLkNoYXJnZSwNCiAgICAgICI8YnI+U3RhYmlsaXR5IGNoYXJnZSBtZWRpYW46Iiwgcm91bmQoc3RhYmlsaXR5X2NoYXJnZV9tZWRpYW4sIDQpLA0KICAgICAgIjxicj5TdGFiaWxpdHkgY2hhcmdlIG1lYW46Iiwgcm91bmQoc3RhYmlsaXR5X2NoYXJnZV9tZWFuLCA0KSwNCiAgICAgICI8YnI+TWluIFN0YWJpbGl0eToiLCByb3VuZChzdGFiaWxpdHlfY2hhcmdlX21pbiwgNCksDQogICAgICAiPGJyPk1heCBTdGFiaWxpdHk6Iiwgcm91bmQoc3RhYmlsaXR5X2NoYXJnZV9tYXgsIDQpDQogICAgKSwNCiAgICBzaXplID0gdG90YWwsIGNvbG9yID0gc3RhYmlsaXR5X2NoYXJnZV9tZWRpYW4NCiAgKSwgYWxwaGEgPSAwLjcpICsNCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQobG93ID0gImJsdWUiLCBoaWdoID0gInJlZCIpICsNCiAgc2NhbGVfc2l6ZV9jb250aW51b3VzKHJhbmdlID0gYygyLCAxMCkpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJNZWRpYW5hIHN0YWJpbG5vxZtjaSB3IHN0YW5pZSBuYcWCYWRvd2FueW0gPGJyPmRsYSBuYWpjesSZc3RzenljaCB3em9yw7N3IGNoZW1pY3pueWNoIG1hdGVyaWHFgsOzdyBiYXRlcmlpIiwNCiAgICB4ID0gIkxpY3piYSBvYnNlcndhY2ppIiwNCiAgICB5ID0gIk1lZGlhbmEgc3RhYmlsbm/Fm2NpIHcgc3RhbmllIG5hxYJhZG93YW55bSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKA0KICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpDQogICkNCg0KZ2dwbG90bHkocCwgdG9vbHRpcCA9ICJ0ZXh0IikNCg0KYGBgDQoNCiMjIEtvcmVsYWNqYSB6bWllbm55Y2gNCg0KUG9uacW8c3phIG1hY2llcnoga29yZWxhY2ppIGlsdXN0cnVqZSB3c3DDs8WCY3p5bm5pa2kga29yZWxhY2ppIFBlYXJzb25hIGRsYSB3eWJyYW55Y2ggYXRyeWJ1dMOzdy4gS29sb3J5IGthZmVsa8OzdyByZXByZXplbnR1asSFIHNpxYLEmSBvcmF6IGtpZXJ1bmVrIGtvcmVsYWNqaS4gT2RjaWVuaWUgbmllYmllc2tpZWdvIHdza2F6dWrEhSBuYSBkb2RhdG5pxIUga29yZWxhY2rEmSwgYSBvZGNpZW5pZSBjemVyd29uZWdvIG5hIHVqZW1uxIUuDQoNCmBgYHtyIGNvb3JlbGF0aW9uLW1hdHJpeCwgIGZpZy53aWR0aD0xNiwgZmlnLmhlaWdodD04fQ0KbnVtYmVyX2F0dHJfZGF0YSA8LSBzZWxlY3QoZGF0YSwgKG51bWVyaWNhbF9hdHRycykpDQoNCmNvcl9tYXRyaXggPC0gY29yKG51bWJlcl9hdHRyX2RhdGEsIG1ldGhvZCA9ICJwZWFyc29uIikNCg0KY29ycnBsb3QubWl4ZWQoY29yX21hdHJpeCwNCiAgdGwucG9zID0gJ2x0JywNCiAgb3JkZXIgPSAnQU9FJywNCiAgbG93ZXIgPSAnc2hhZGUnLA0KICB1cHBlciA9ICdudW1iZXInDQopDQpgYGANCioqTmFqd3nFvHN6eSB3c3BvbGN6eW5uaWsga29yZWxhY2ppKiogd3lzdGVwdWplIHBvbWllZHp5IHBhcmFtaSBhdHJ5YnV0w7N3OlwNCi0gKipHcmF2aW1ldHJpYyBFbmVyZ3kgaSBWb2x1bWV0cmljIEVuZXJneSoqIC0gMC45MyBcDQotICoqR3JhdmltZXRyaWMgQ2FwY2l0eSBpIFZvbHVtZXRyaWMgQ2FwYWNpdHkqKiAtIDAuODYgXA0KLSAqKlN0YWJpbGl0eSBDaGFyZ2UgaSBTdGFiaWxpdHkgRGlzY2hhcmdlKiogLSAwLjgwIFwNCi0gR3JhdmltZXRyaWMgQ2FwYWNpdHkgaSBBdG9taWMgRnJhY3Rpb24gRGlzY2hhcmdlIC0gMC42OCBcDQotIEF2ZXJhZ2UgVm9sdGFnZSBpIEdyYXZpbWV0cmljIEVuZXJneSAtIDAuNjcNCg0KIyMjIEtvcmVsYWNqYSBwb21pxJlkenkgZW5lcmdpxIUgZ3Jhd2ltZXRyeWN6bsSFIGkgd29sdW1ldHJ5Y3puxIUNCg0KYGBge3IgfQ0KZ3JhdmltZXRyaWNfdm9sdW1ldHJpY19lbmVyZ3lfY29ycmVsYXRpb24gPC0gY29yKGRhdGEkR3JhdmltZXRyaWMuRW5lcmd5LCBkYXRhJFZvbHVtZXRyaWMuRW5lcmd5LCB1c2UgPSAiY29tcGxldGUub2JzIikNCg0KZ2dwbG90KGRhdGEsIGFlcyh4ID0gR3JhdmltZXRyaWMuRW5lcmd5LCB5ID0gVm9sdW1ldHJpYy5FbmVyZ3kpKSArDQogIGdlb21fcG9pbnQoY29sb3IgPSAiZGFya2dyZWVuIiwgc2l6ZSA9IDIsIGFscGhhID0gMC43KSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIGNvbG9yID0gImJsdWUiLCBzZSA9IEZBTFNFLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsb2VzcyIsIGNvbG9yID0gInJlZCIsIHNlID0gVFJVRSwgZmlsbCA9ICJncmF5ODAiKSArDQogIGxhYnModGl0bGUgPSAiS29yZWxhY2phIG1pxJlkenkgZ3Jhd2ltZXRyeWN6bsSFIGkgd29sdW1ldHJ5Y3puxIUgZ8SZc3RvxZtjacSFIGVuZXJnaWkiLA0KICAgICAgIHN1YnRpdGxlID0gcGFzdGUoIldzcMOzxYJjenlubmlrIGtvcmVsYWNqaSA9Iiwgcm91bmQoZ3JhdmltZXRyaWNfdm9sdW1ldHJpY19lbmVyZ3lfY29ycmVsYXRpb24sIDIpKSwNCiAgICAgICB4ID0gIkdyYXdpbWV0cnljem5hIGfEmXN0b8WbxIcgZW5lcmdpaSAoV2gva2cpIiwNCiAgICAgICB5ID0gIldvbHVtZXRyeWN6bmEgZ8SZc3RvxZvEhyBlbmVyZ2lpIChXaC9MKSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KV3lrcmVzIHByemVkc3Rhd2lhICoqemFsZcW8bm/Fm8SHIG1pxJlkenkgd29sdW1ldHJ5Y3puxIUqKiAoV2gvTCwgZW5lcmdpYSBuYSBqZWRub3N0a8SZIG9iasSZdG/Fm2NpKSBpICoqZ3Jhd2ltZXRyeWN6bsSFKiogKFdoL2tnLCBlbmVyZ2lhIG5hIGplZG5vc3RrxJkgbWFzeSkgZ8SZc3RvxZtjacSFIGVuZXJnaWksIGdkemllIHdpZG9jem5hIGplc3QgKipzaWxuYSBrb3JlbGFjamEqKiBkb2RhdG5pYSBtacSZZHp5IHR5bWkgcGFyYW1ldHJhbWkuIEfEmXN0b8WbxIcgZW5lcmdpaSBqZXN0ICoqa2x1Y3pvd3ltIHdza2HFum5pa2llbSB3eWRham5vxZtjaSBiYXRlcmlpKiogLSBpbSB3ecW8c3phIHdhcnRvxZvEhywgdHltIHdpxJljZWogZW5lcmdpaSBtb8W8ZSBiecSHIHptYWdhenlub3dhbmUgdyBkYW5laiBvYmrEmXRvxZtjaSBsdWIgbWFzaWUgYmF0ZXJpaSwgY28gamVzdCBzemN6ZWfDs2xuaWUgaXN0b3RuZSB3IHphc3Rvc293YW5pYWNoIG1vYmlsbnljaCwgdGFraWNoIGphayBwb2phemR5IGVsZWt0cnljem5lIGN6eSB1cnrEhWR6ZW5pYSBwcnplbm/Fm25lLiAqKldpxJlrc3pvxZvEhyBiYWRhbnljaCBtYXRlcmlhxYLDs3cgc2t1cGlhIHNpxJkgdyB6YWtyZXNpZSBkbyAyMDAwIFdoL2tnIGkgNzUwMCBXaC9MKiosIGNob8SHIHd5c3TEmXB1amUga2lsa2Egb2JpZWN1asSFY3ljaCB3eWrEhXRrw7N3IG8gd3nFvHN6eWNoIHBhcmFtZXRyYWNoLCBrdMOzcmUgbW9nxIUgc3Rhbm93acSHIHBvdGVuY2phbG5lIGtpZXJ1bmtpIHJvendvanUgbm93eWNoLCB3eWRham5pZWpzenljaCBiYXRlcmlpLg0KDQojIyMgS29yZWxhY2phIHBvbWnEmWR6eSBwb2plbW5vxZtjacSFIGdyYXdpbWV0cnljem7EhSBpIHdvbHVtZXRyeWN6bsSFDQoNCmBgYHtyfQ0KZ3JhdmltZXRyaWNfdm9sdW1ldHJpY19jYXBhY2l0eV9jb3JyZWxhdGlvbiA8LSBjb3IoZGF0YSRHcmF2aW1ldHJpYy5DYXBhY2l0eSwgZGF0YSRWb2x1bWV0cmljLkNhcGFjaXR5LCB1c2UgPSAiY29tcGxldGUub2JzIikNCg0KZ2dwbG90KGRhdGEsIGFlcyh4ID0gR3JhdmltZXRyaWMuQ2FwYWNpdHksIHkgPSBWb2x1bWV0cmljLkNhcGFjaXR5KSkgKw0KICBnZW9tX3BvaW50KGNvbG9yID0gImRhcmtncmVlbiIsIHNpemUgPSAyLCBhbHBoYSA9IDAuNykgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBjb2xvciA9ICJibHVlIiwgc2UgPSBGQUxTRSkgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG9lc3MiLCBjb2xvciA9ICJyZWQiLCBzZSA9IFRSVUUsIGZpbGwgPSAiZ3JheTgwIikgKw0KICBsYWJzKHRpdGxlID0gIktvcmVsYWNqYSBtacSZZHp5IGdyYXdpbWV0cnljem7EhSBpIHdvbHVtZXRyeWN6bsSFIHBvamVtbm/Fm2NpxIUgIiwNCiAgICAgICBzdWJ0aXRsZSA9IHBhc3RlKCJDb3JyZWxhdGlvbiA9Iiwgcm91bmQoZ3JhdmltZXRyaWNfdm9sdW1ldHJpY19jYXBhY2l0eV9jb3JyZWxhdGlvbiwgMikpLA0KICAgICAgIHggPSAiUG9qZW1ub8WbxIcgZ3Jhd2ltZXRyeWN6bmEgKG1BaC9nKSIsDQogICAgICAgeSA9ICJQb2plbW5vxZvEhyB3b2x1bWV0cnljem5hIChtQWgvY23CsykiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQpgYGANCg0KDQpXeWtyZXMgcHJ6ZWRzdGF3aWEgKip6YWxlxbxub8WbxIcgbWnEmWR6eSB3b2x1bWV0cnljem7EhSoqIChtQWgvY23CsywgaWxvxZvEhyDFgmFkdW5rdSBuYSBqZWRub3N0a8SZIG9iasSZdG/Fm2NpKSBpICoqZ3Jhd2ltZXRyeWN6bsSFKiogKG1BaC9nLCBpbG/Fm8SHIMWCYWR1bmt1IG5hIGplZG5vc3RrxJkgbWFzeSkgcG9qZW1ub8WbY2nEhS4gUG9qZW1ub8WbxIcgZ3Jhd2ltZXRyeWN6bmEgb2tyZcWbbGEgaWxlIGVuZXJnaWkgbW/FvG5hIHptYWdhenlub3dhxIcgdyBkYW5laiBtYXNpZSBtYXRlcmlhxYJ1LCBhIHdvbHVtZXRyeWN6bmEgLSBpbGUgdyBkYW5laiBvYmrEmXRvxZtjaSwgY28gbWEga2x1Y3pvd2Ugem5hY3plbmllIHByenkgcHJvamVrdG93YW5pdSBiYXRlcmlpIG8gcsOzxbxueW0gcHJ6ZXpuYWN6ZW5pdS4gKipXc3DDs8WCY3p5bm5payBrb3JlbGFjamkgMC44NiB3c2thenVqZSBuYSBzaWxuxIUgemFsZcW8bm/Fm8SHIG1pxJlkenkgdHltaSBwYXJhbWV0cmFtaSoqLCBjaG/EhyBuaWUgdGFrIHNpbG7EhSBqYWsgdyBwcnp5cGFka3UgZ8SZc3RvxZtjaSBlbmVyZ2lpLiBOYSBwcnp5a8WCYWQsIG1hdGVyaWHFgiBvIHd5c29raWVqIHBvamVtbm/Fm2NpIGdyYXdpbWV0cnljem5laiBtb8W8ZSBiecSHIGxla2tpLCBhbGUgemFqbW93YcSHIGR1xbxvIG1pZWpzY2EsIHBvZGN6YXMgZ2R5IG1hdGVyaWHFgiBvIHd5c29raWVqIHBvamVtbm/Fm2NpIHdvbHVtZXRyeWN6bmVqIG1vxbxlIGJ5xIcgY2nEmcW8c3p5LCBhbGUgYmFyZHppZWoga29tcGFrdG93eSBbQG9nbml3YS1saXRvd28tam9ub3dlXS4NCg0KDQojIyMgS29yZWxhY2phIHBvbWnEmWR6eSBzdGFiaWxub8WbY2nEhSBtYXRlcmlhxYJ1IHcgc3RhbmllIG5hxYJhZG93YW55bSBpIHJvesWCYWRvd2FueW0NCg0KYGBge3J9DQpzdGFiaWxpdHlfY2hhcmdlX2Rpc2NoYXJnZV9jb29yZWxhdGlvbiA8LSBjb3IoZGF0YSRTdGFiaWxpdHkuQ2hhcmdlLCBkYXRhJFN0YWJpbGl0eS5EaXNjaGFyZ2UsIHVzZSA9ICJjb21wbGV0ZS5vYnMiKQ0KDQpnZ3Bsb3QoZGF0YSwgYWVzKHggPSBTdGFiaWxpdHkuQ2hhcmdlLCB5ID0gU3RhYmlsaXR5LkRpc2NoYXJnZSkpICsNCiAgZ2VvbV9wb2ludChjb2xvciA9ICJkYXJrZ3JlZW4iLCBzaXplID0gMiwgYWxwaGEgPSAwLjcpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgY29sb3IgPSAiYmx1ZSIsIHNlID0gRkFMU0UsIGxpbmV0eXBlID0gImRhc2hlZCIpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxvZXNzIiwgY29sb3IgPSAicmVkIiwgc2UgPSBUUlVFLCBmaWxsID0gImdyYXk4MCIpICsNCiAgbGFicyh0aXRsZSA9ICJLb3JlbGFjamEgbWF0ZXJpYcWCdSB3IHN0YW5pZSBuYcWCYWRvd2FueW0gaSByb3rFgmFkb3dhbnltIiwNCiAgICAgICBzdWJ0aXRsZSA9IHBhc3RlKCJDb3JyZWxhdGlvbiA9Iiwgcm91bmQoc3RhYmlsaXR5X2NoYXJnZV9kaXNjaGFyZ2VfY29vcmVsYXRpb24sIDIpKSwNCiAgICAgICB4ID0gIlN0YWJpbG5vxZvEhyB3IHN0YW5pZSBuYcWCYWRvd2FueW0iLA0KICAgICAgIHkgPSAiU3RhYmlsbm/Fm8SHIHcgc3RhbmllIHJvesWCYWRvd2FueW0iKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQpgYGANCg0KDQpXeWtyZXMgcHJ6ZWRzdGF3aWEgKip6YWxlxbxub8WbxIcgbWnEmWR6eSBzdGFiaWxub8WbY2nEhSBtYXRlcmlhxYJ1IHcgc3RhbmllIG5hxYJhZG93YW55bSoqIChTdGFiaWxpdHkgQ2hhcmdlKSBpICoqdyBzdGFuaWUgcm96xYJhZG93YW55bSoqIChTdGFiaWxpdHkgRGlzY2hhcmdlKSwgemUgd3Nww7PFgmN6eW5uaWtpZW0ga29yZWxhY2ppIDAuOCB3c2thenVqxIVjeW0gbmEgc2lsbsSFIGRvZGF0bmnEhSB6YWxlxbxub8WbxIcuIFN0YWJpbG5vxZvEhyBtYXRlcmlhxYJ1IGplc3QgKiprbHVjem93eW0gcGFyYW1ldHJlbSBva3JlxZtsYWrEhWN5bSwgamFrIGRvYnJ6ZSBtYXRlcmlhxYIgemFjaG93dWplIHN3b2rEhSBzdHJ1a3R1csSZIGkgd8WCYcWbY2l3b8WbY2kgcG9kY3phcyBjeWtsaSDFgmFkb3dhbmlhIGkgcm96xYJhZG93YW5pYSoqIC0gaW0gbmnFvHN6YSB3YXJ0b8WbxIcsIHR5bSBtYXRlcmlhxYIgamVzdCBiYXJkemllaiBzdGFiaWxueSBpIGJlenBpZWN6bnkgdyB1xbx5dGtvd2FuaXUuIFdpxJlrc3pvxZvEhyBiYWRhbnljaCBtYXRlcmlhxYLDs3cgc2t1cGlhIHNpxJkgdyB6YWtyZXNpZSBuaXNraWNoIHdhcnRvxZtjaSAoMC0yKSBkbGEgb2J1IHBhcmFtZXRyw7N3LCBjbyBqZXN0IHBvxbzEhWRhbmUsIG5hdG9taWFzdCBwdW5rdHkgb2RzdGFqxIVjZSBvIHd5xbxzenljaCB3YXJ0b8WbY2lhY2ggKHBvd3nFvGVqIDQpIG1vZ8SFIHdza2F6eXdhxIcgbmEgbWF0ZXJpYcWCeSBwcm9ibGVtYXR5Y3puZSwga3TDs3JlIG1vZ8SFIGJ5xIcgbW5pZWogb2Rwb3dpZWRuaWUgZG8gemFzdG9zb3dhxYQgdyBiYXRlcmlhY2ggemUgd3pnbMSZZHUgbmEgcG90ZW5jamFsbsSFIG5pZXN0YWJpbG5vxZvEhy4NCg0KIyMjIEtvcmVsYWNqYSBwb21pxJlkenkgcG9qZW1ub8WbY2nEhSBncmF3aW1ldHJ5Y3puxIUgaSB1ZHppYcWCIGF0b21vd3ltIHNrxYJhZG5pa8OzdyB3IHN0YW5pZSByb3rFgmFkb3dhbnltDQoNCmBgYHtyIGNvb3JlbGF0aW9uLWdyYXZpbWV0cmljLWNhcGFjaXR5LWF0b21pYy1ncmFjdGlvbi1kaXNjaGFyZ2V9DQpncmF2aW1ldHJpY19jYXBhY2l0eV9hdG9taWNfZnJhY3Rpb25fZGlzY2hhcmdlX2Nvb3JlbGF0aW9uIDwtIGNvcihkYXRhJEdyYXZpbWV0cmljLkNhcGFjaXR5LCBkYXRhJEF0b21pYy5GcmFjdGlvbi5EaXNjaGFyZ2UsIHVzZSA9ICJjb21wbGV0ZS5vYnMiKQ0KDQpnZ3Bsb3QoZGF0YSwgYWVzKHggPSBHcmF2aW1ldHJpYy5DYXBhY2l0eSwgeSA9IEF0b21pYy5GcmFjdGlvbi5EaXNjaGFyZ2UpKSArDQogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gQXRvbWljLkZyYWN0aW9uLkRpc2NoYXJnZSksIHNpemUgPSAzLCBhbHBoYSA9IDAuNykgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG9lc3MiLCBjb2xvciA9ICJyZWQiLCBzZSA9IFRSVUUsIGZpbGwgPSAiZ3JheTgwIikgKw0KICBsYWJzKHRpdGxlID0gIktvcmVsYWNqYSBtacSZZHp5IHBvamVtbm5vxZtjacSFIGdyYXdpbWV0cnljem7EhSBhIHVkemlhxYIgYXRvbW93eW0gc2vFgmFkbmlrw7N3IHcgc3RhbmllIHJvesWCYWRvd2FueW0iLA0KICAgICAgIHN1YnRpdGxlID0gcGFzdGUoIkNvcnJlbGF0aW9uID0iLCByb3VuZChncmF2aW1ldHJpY19jYXBhY2l0eV9hdG9taWNfZnJhY3Rpb25fZGlzY2hhcmdlX2Nvb3JlbGF0aW9uLCAyKSksDQogICAgICAgeCA9ICJQb2plbW5vxZvEhyBncmF3aW1ldHJ5Y3puYSAobUFoL2cpIiwNCiAgICAgICB5ID0gIlVkemlhxYIgYXRvbW93eSBza8WCYWRuaWvDs3cgdyBzdGFuaWUgcm96xYJhZG93YW55bS4iKSArDQogIHNjYWxlX2NvbG9yX2dyYWRpZW50KGxvdyA9ICJvcmFuZ2UiLCBoaWdoID0gInB1cnBsZSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCmBgYA0KDQoNCld5a3JlcyBwcnplZHN0YXdpYSAqKnphbGXFvG5vxZvEhyBtacSZZHp5IHBvamVtbm/Fm2NpxIUgZ3Jhd2ltZXRyeWN6bsSFKiogKEdyYXZpbWV0cmljIENhcGFjaXR5LCBtQWgvZykgaSAqKnVkemlhxYJlbSBhdG9tb3d5bSB3IHN0YW5pZSByb3rFgmFkb3dhbmlhKiogKEF0b21pYyBGcmFjdGlvbiBEaXNjaGFyZ2UpLiBNb8W8bmEgemFvYnNlcndvd2HEhyB1bWlhcmtvd2FuaWUgc2lsbsSFIGRvZGF0bmnEhSB6YWxlxbxub8WbxIcsIGNvIHBvdHdpZXJkemEgd3Nww7PFgmN6eW5uaWsga29yZWxhY2ppIHd5bm9zesSFY3kgMC42OC4gVyBtaWFyxJkgd3pyb3N0dSBwb2plbW5vxZtjaSBncmF3aW1ldHJ5Y3puZWosIHVkemlhxYIgYXRvbW93eSB3IHN0YW5pZSByb3rFgmFkb3dhbmlhIHp3acSZa3N6YSBzacSZLCBvc2nEhWdhasSFYyB3YXJ0b8WbxIcgbWFrc3ltYWxuxIUgYmxpc2vEhSAxLjAuIFwNCktvbG9yIHB1bmt0w7N3IHJlcHJlemVudHVqZSB3YXJ0b8WbxIcgQXRvbWljIEZyYWN0aW9uIERpc2NoYXJnZSwgZ2R6aWUgamHFm25pZWpzemUga29sb3J5IHdza2F6dWrEhSBuYSBuacW8c3plIHdhcnRvxZtjaSwgYSBjaWVtbmllanN6ZSBuYSB3ecW8c3plLiBEYW5lIHdza2F6dWrEhSwgxbxlIHdpxJlrc3pvxZvEhyBvYnNlcndhY2ppIHpuYWpkdWplIHNpxJkgdyB6YWtyZXNpZSBuaXNraWVqIHBvamVtbm/Fm2NpIGdyYXdpbWV0cnljem5laiAoPDEwMDAgbUFoL2cpLCBhIGRsYSB3YXJ0b8WbY2kgcG93ecW8ZWogMjAwMCBtQWgvZyB6YWxlxbxub8WbxIcgc3RhamUgc2nEmSBuaWVsaW5pb3dhLiBTdWdlcnVqZSB0bywgxbxlIG1hdGVyaWHFgnkgbyB3ecW8c3plaiBwb2plbW5vxZtjaSBncmF3aW1ldHJ5Y3puZWogbWFqxIUgdGVuZGVuY2rEmSBkbyBvc2nEhWdhbmlhIHd5xbxzenljaCB1ZHppYcWCw7N3IGF0b21vd3ljaCB3IHN0YW5pZSByb3rFgmFkb3dhbmlhLg0KDQojIyMgS29yZWxhY2phIHBvbWnEmWR6eSDFm3JlZG5pbSBuYXBpxJljaWVtIGEgZW5lcmdpxIUgZ3Jhd2ltZXRyeWN6bsSFDQoNCmBgYHtyfQ0KZmlsdGVyZWREYXRhIDwtIGRhdGEgJT4lZmlsdGVyKGRhdGEkU3RlcHMgPT0gMykNCmNvcnJlbGF0aW9uIDwtIGNvcihkYXRhJEF2ZXJhZ2UuVm9sdGFnZSwgZGF0YSRHcmF2aW1ldHJpYy5FbmVyZ3ksIHVzZSA9ICJjb21wbGV0ZS5vYnMiKQ0KDQpnZ3Bsb3QoZGF0YSwgYWVzKHggPSBBdmVyYWdlLlZvbHRhZ2UsIHkgPSBHcmF2aW1ldHJpYy5FbmVyZ3kpKSArDQogIGdlb21fcG9pbnQoY29sb3IgPSAiZGFya2JsdWUiLCBzaXplID0gMywgYWxwaGEgPSAwLjcpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgY29sb3IgPSAiZGFya3JlZCIsIHNlID0gRkFMU0UsIGxpbmV0eXBlID0gImRhc2hlZCIpICsNCiAgbGFicyh0aXRsZSA9ICJLb3JlbGFjamEgbWnEmWR6eSDFm3JlZG5pbSBuYXBpxJljaWVtIGkgZW5lcmdpxIUgZ3Jhd2ltZXRyeWN6bsSFIiwNCiAgICAgICBzdWJ0aXRsZSA9IHBhc3RlKCJDb3JyZWxhdGlvbiA9Iiwgcm91bmQoY29ycmVsYXRpb24sIDIpKSwNCiAgICAgICB4ID0gIsWacmVkbmllIG5hcGnEmWNpZSAoVikiLA0KICAgICAgIHkgPSAiRW5lcmdpYSBncmF3aW1ldHJ5Y3puYSAoV2gva2cpIikgKw0KICB0aGVtZV9jbGFzc2ljKCkNCg0KYGBgDQoNCg0KYGBge3IgYXZlcmFnZS12b2x0YWdlLWdyYXZpbWV0cmljLWVuZXJneS1ieS1zdGVwLCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9MTB9DQoNCmRhdGFfd2l0aF9jb3IgPC0gZGF0YSAlPiUNCiAgZ3JvdXBfYnkoU3RlcHMpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgY29ycmVsYXRpb24gPSBjb3IoQXZlcmFnZS5Wb2x0YWdlLCBHcmF2aW1ldHJpYy5FbmVyZ3ksIHVzZSA9ICJjb21wbGV0ZS5vYnMiKSwNCiAgICBuX2Nhc2VzID0gbigpIA0KICApDQoNCnAgPC0gZ2dwbG90KGRhdGEsIGFlcyh4ID0gQXZlcmFnZS5Wb2x0YWdlLCB5ID0gR3JhdmltZXRyaWMuRW5lcmd5KSkgKw0KICAgZ2VvbV9wb2ludChhZXModGV4dCA9IHBhc3RlKCJTdGVwOiIsIFN0ZXBzLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICI8YnI+Vm9sdGFnZToiLCBBdmVyYWdlLlZvbHRhZ2UsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjxicj5FbmVyZ3k6IiwgR3JhdmltZXRyaWMuRW5lcmd5LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICI8YnI+SW9uOiIsIFdvcmtpbmcuSW9uKSksIA0KICAgICAgICAgICAgIGNvbG9yID0gImRhcmtibHVlIiwgc2l6ZSA9IDMsIGFscGhhID0gMC43KSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIGNvbG9yID0gImRhcmtyZWQiLCBzZSA9IEZBTFNFLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGZhY2V0X3dyYXAofiBTdGVwcywgc2NhbGVzID0gImZpeGVkIiwgICBuY29sID0gMiwgDQogICAgICAgICAgICAgbGFiZWxsZXIgPSBsYWJlbGxlcihTdGVwcyA9IGZ1bmN0aW9uKHgpIHsNCiAgICAgICAgICAgICAgcm93IDwtIGRhdGFfd2l0aF9jb3JbZGF0YV93aXRoX2NvciRTdGVwcyA9PSB4LCBdDQogICAgICAgICAgICAgIHBhc3RlMCgiU3RlcCA9ICIsIHgsICIgKENvcnIgPSAiLCByb3VuZChyb3ckY29ycmVsYXRpb24sIDIpLCANCiAgICAgICAgICAgICAgICAgICAgICAiLCBuID0gIiwgcm93JG5fY2FzZXMsICIpIikNCiAgICAgICAgICAgICB9KSkgKw0KICAgbGFicyh0aXRsZSA9ICLFmnJlZG5pZSBuYXBpxJljaWUgYSBlbmVyZ2lhIGdyYXdpbWV0cnljem5hIHByenkgdXd6Z2zEmWRuaWVuaXUgd2FydG/Fm2NpIGtyb2t1IiwNCiAgICAgICB4ID0gIsWacmVkbmllIG5hcGnEmWNpZSAoVikiLA0KICAgICAgIHkgPSAiRW5lcmdpYSBncmF3aW1ldHJ5Y3puYSAoV2gva2cpIikgICsNCiAgdGhlbWVfZ3JleSgpICsNCiB0aGVtZSgNCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KG1hcmdpbiA9IGdncGxvdDI6Om1hcmdpbih0ID0gMTAsIGIgPSAxMCksIGhqdXN0ID0gMC41KSwNCiAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQobWFyZ2luID0gZ2dwbG90Mjo6bWFyZ2luKHQgPSAxMCkpLA0KICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChtYXJnaW4gPSBnZ3Bsb3QyOjptYXJnaW4ociA9IDEwKSksDQogICAgcGxvdC5tYXJnaW4gPSBnZ3Bsb3QyOjptYXJnaW4oMjAsIDIwLCAyMCwgMjApLA0KICAgIHBhbmVsLnNwYWNpbmcgPSB1bml0KDIsICJjbSIpDQogICkNCg0KZ2dwbG90bHkocCwgIHRvb2x0aXAgPSAidGV4dCIpDQpgYGANCg0KDQpXeWtyZXN5IHByemVkc3Rhd2lhasSFIHphbGXFvG5vxZtjaSBtacSZZHp5IMWbcmVkbmltIG5hcGnEmWNpZW0gKEF2ZXJhZ2UgVm9sdGFnZSwgVikgYSBnxJlzdG/Fm2NpxIUgZW5lcmdpaSBncmF3aW1ldHJ5Y3puZWogKEdyYXZpbWV0cmljIEVuZXJneSBEZW5zaXR5LCBXaC9rZykgZGxhIHLDs8W8bnljaCB3YXJ0b8WbY2kga3Jva3UgbmFwacSZY2lhLiBOYWp3acSZY2VqIG9ic2Vyd2FjamkgamVzdCBkbGEga3Jva3UgcsOzd25lZ28gMSBpIGplc3QgdG8gbmFqYmFyZHppZWogcmVwcmV6ZW50YXR5d25hIHByw7Nia2EuIFcgdHltIHByenlwYWRrdSB3c3DDs8WCY3p5bm5payBrb3JlbGFjamkgamVzdCBuYSB3eXNva2ltIHBvemlvbWllLCBibyB3eW5vc2kgMC42OS4gDQpEYW5lIHdza2F6dWrEhSwgxbxlIHdyYXogemUgd3pyb3N0ZW0gxZtyZWRuaWVnbyBuYXBpxJljaWEgendpxJlrc3phIHNpxJkgZ8SZc3RvxZvEhyBlbmVyZ2lpIGdyYXdpbWV0cnljem5lai4gV2nEmWtzem/Fm8SHIGRhbnljaCBza3VwaWEgc2nEmSB3IHpha3Jlc2llIG5pc2tpY2ggd2FydG/Fm2NpIG5hcGnEmWNpYSAoPDEwIFYpLCBjbyBzdWdlcnVqZSwgxbxlIG1hdGVyaWHFgnkgbyB3ecW8c3p5bSBuYXBpxJljaXUgc8SFIG1uaWVqIGxpY3puZSwgYWxlIG1vZ8SFIHd5a2F6eXdhxIcgd2nEmWtzesSFIGVmZWt0eXdub8WbxIcgZW5lcmdldHljem7EhS4NCg0KDQojIFByZWR5a2NqYSBjZWNoIGkgd8WCYcWbY2l3b8WbY2kgYmF0ZXJpaQ0KDQpXIHRlaiBzZWtjamkgem9zdGFuxIUgcHJ6ZWFuYWxpem93YW5lIGR3YSBtb2RlbGUgcHJlZHlrY3lqbmUgZG90eWN6xIVjZSB3xYJhxZtjaXdvxZtjaSBiYXRlcmlpLiBQaWVyd3N6eSBtb2RlbCwgb3BhcnR5IG5hIHJlZ3Jlc2ppIGxpbmlvd2VqLCBza3VwaWEgc2nEmSBuYSBwcnpld2lkeXdhbml1IGVuZXJnaWkgZ3Jhd2ltZXRyeWN6bmVqLiBEcnVnaSBtb2RlbCwgd3lrb3J6eXN0dWrEhWN5IGFsZ29yeXRtIFJhbmRvbSBGb3Jlc3QsIHByemV3aWR1amUgxZtyZWRuaWUgbmFwacSZY2llLiBXIG9idSBwcnp5cGFka2FjaCBjZWxlbSBqZXN0IG9jZW5hIHNrdXRlY3pub8WbY2kgemFzdG9zb3dhbnljaCBtZXRvZCBvcmF6IGlkZW50eWZpa2FjamEga2x1Y3pvd3ljaCBjenlubmlrw7N3IHdwxYJ5d2FqxIVjeWNoIG5hIHd5bmlraS4NCg0KIyMgUHJlZHlrY2phIGVuZXJnaWkgZ3Jhd2ltZXRyeWN6bmVqIG1ldG9kxIUgcmVncmVzamkNCg0KIyMjIFByZXByb2Nlc3NpbmcgZGFueWNoDQoNClByZXByb2Nlc3NpbmcgZGFueWNoIGplc3Qga2x1Y3pvd3ltIGV0YXBlbSBwcnp5Z290b3dhbmlhIHpiaW9ydSB0cmVuaW5nb3dlZ28sIHBvbmlld2HFvCB3cMWCeXdhIGJlenBvxZtyZWRuaW8gbmEgamFrb8WbxIcsIHByZWN5emrEmSBpIHpkb2xub8WbxIcgcHJlZHlrY3lqbsSFIG1vZGVsdSwgZWxpbWludWrEhWMgc3p1bXksIHJlZHVuZGFuY2plIGkgcG90ZW5jamFsbmUgxbpyw7NkxYJhIGLFgsSZZMOzdywgY28gb3N0YXRlY3puaWUgZGVjeWR1amUgbyBza3V0ZWN6bm/Fm2NpIGkgd2lhcnlnb2Rub8WbY2kgY2HFgmVnbyBtb2RlbHUuDQoNCmBgYHtyfQ0KYXR0cmlidXRlc190b19yZW1vdmUgPC0gY29sbmFtZXMoc2VsZWN0KGRhdGEsIGMoQmF0dGVyeS5JRCwgQmF0dGVyeS5Gb3JtdWxhLCBGb3JtdWxhLkRpc2NoYXJnZSkpKQ0KYGBgDQoNClcgcGllcndzenltIGtyb2t1IHVzdW5pxJl0byB3eWJyYW5lIGtvbHVtbnkgdGFraWUgamFrIGByIGF0dHJpYnV0ZXNfdG9fcmVtb3ZlYCwga3TDs3JlIG5pZSB3bm9zacWCeSBiZXpwb8WbcmVkbmljaCBpbmZvcm1hY2ppIG51bWVyeWN6bnljaCBkbyBtb2RlbG93YW5pYS4NCg0KYGBge3J9DQpiYXR0ZXJ5X3RyYWluX2RhdGEgPC0gc2VsZWN0KGRhdGEsIC0oYXR0cmlidXRlc190b19yZW1vdmUpKQ0KY29yX3RoZXJlc2hvbGQgPC0gMC44DQpjb3Jfd2l0aF90YXJnZXQgPC0gY29yKGRhdGFzZXRfbnVtZXJpY2FsX2F0dHJzLCB1c2UgPSAicGFpcndpc2UuY29tcGxldGUub2JzIilbLCAiVm9sdW1ldHJpYy5FbmVyZ3kiXQ0KaGlnaF9jb3JfdG9fdGFyZ2V0IDwtIG5hbWVzKHdoaWNoKGNvcl93aXRoX3RhcmdldCA+IDAuOCkpDQpiYXR0ZXJ5X3RyYWluX2RhdGEgPC0gYmF0dGVyeV90cmFpbl9kYXRhICU+JQ0KICBzZWxlY3QoLWFsbF9vZihoaWdoX2Nvcl90b190YXJnZXQpLCBWb2x1bWV0cmljLkVuZXJneSkNCg0KYGBgDQoNCk5hc3TEmXBuaWUsIG9ibGljem9ubyBrb3JlbGFjasSZIHptaWVubnljaCBudW1lcnljem55Y2ggemUgem1pZW5uxIUgY2VsdSwgemlkZW50eWZpa293YW5vIGkgdXN1bmnEmXRvIHptaWVubmUgbyB3eXNva2llaiBrb3JlbGFjamkgbGluaW93ZWogKHByemVrcmFjemFqxIVjZWogd2FydG/Fm8SHIGByIGNvcl90aGVyZXNob2xkYCkuIEF0cnlidXR5LCBrdMOzcmUgem9zdGHFgnkgdXN1bmnEmXRlIHRvIGByIGhpZ2hfY29yX3RvX3RhcmdldGAuDQoNCmBgYHtyfQ0KDQpvdXRsaWVyX3RocmVzaG9sZCA8LSAyDQoNCmRldGVjdF9vdXRsaWVycyA8LSBmdW5jdGlvbih4KSB7DQogIFExIDwtIHF1YW50aWxlKHgsIDAuMjUsIG5hLnJtID0gVFJVRSkNCiAgUTMgPC0gcXVhbnRpbGUoeCwgMC43NSwgbmEucm0gPSBUUlVFKQ0KICBJUVIgPC0gUTMgLSBRMQ0KICByZXR1cm4oeCA8IChRMSAtIDEuNSAqIElRUikgfCB4ID4gKFEzICsgMS41ICogSVFSKSkNCn0NCg0Kb3V0bGllcl9tYXRyaXggPC0gYXMuZGF0YS5mcmFtZShsYXBwbHkoYmF0dGVyeV90cmFpbl9kYXRhLCBmdW5jdGlvbihjb2x1bW4pIHsNCiAgaWYgKGlzLm51bWVyaWMoY29sdW1uKSkgew0KICAgIHJldHVybihkZXRlY3Rfb3V0bGllcnMoY29sdW1uKSkNCiAgfSBlbHNlIHsNCiAgICByZXR1cm4ocmVwKEZBTFNFLCBsZW5ndGgoY29sdW1uKSkpDQogIH0NCn0pKQ0KDQpvdXRsaWVyX2NvdW50IDwtIHJvd1N1bXMob3V0bGllcl9tYXRyaXgpDQpucm93KGJhdHRlcnlfdHJhaW5fZGF0YVtvdXRsaWVyX2NvdW50ID49IG91dGxpZXJfdGhyZXNob2xkLF0pDQoNCmRhdGFfY2xlYW5lZCA8LSBiYXR0ZXJ5X3RyYWluX2RhdGFbb3V0bGllcl9jb3VudCA8IG91dGxpZXJfdGhyZXNob2xkLCBdDQpza2ltKGRhdGFfY2xlYW5lZCkNCmBgYA0KVyBwcm9jZXNpZSBwcmVwcm9jZXNzaW5ndSBkYW55Y2ggamVkbnltIHoga2x1Y3pvd3ljaCBldGFww7N3IGJ5xYJhIGlkZW50eWZpa2FjamEgaSB1c3VuacSZY2llIG9ic2Vyd2Fjamkgb2RzdGFqxIVjeWNoIHcgemJpb3J6ZSBkYW55Y2guIFcgdHltIGNlbHUgemRlZmluaW93YW5vIHByw7NnIGRsYSBsaWN6Ynkgb2RzdGFqxIVjeWNoIHdhcnRvxZtjaSB3IGRhbmVqIG9ic2Vyd2FjamkgKG91dGxpZXJfdGhyZXNob2xkKSwga3TDs3J5IHVzdGFsb25vIG5hIHBvemlvbWllIDIuIE5hc3TEmXBuaWUsIHd5a29yenlzdHVqxIVjIG1ldG9kxJkgb3BhcnTEhSBuYSBhbmFsaXppZSByb3pzdMSZcHUgbWnEmWR6eWt3YXJ0eWxvd2VnbyAoSVFSKSwgemlkZW50eWZpa293YW5vIHdhcnRvxZtjaSBvZHN0YWrEhWNlIHcga2HFvGRlaiBrb2x1bW5pZSBudW1lcnljem5laiB6YmlvcnUgZGFueWNoLg0KDQpGdW5rY2phIHRhIG9ibGljemEgcGllcndzenkgKFExKSBpIHRyemVjaSBrd2FydHlsIChRMykgZGxhIGthxbxkZWogem1pZW5uZWosIGEgbmFzdMSZcG5pZSBva3JlxZtsYSB6YWtyZXMgd2FydG/Fm2NpIHV6bmF3YW55Y2ggemEgbm9ybWFsbmUsIHd5em5hY3pvbnkgcHJ6ZXogcHJ6ZWR6aWHFgiBbUTHiiJIxLjXii4VJUVIsUTMrMS414ouFSVFSXS4gV2FydG/Fm2NpIHNwb3phIHRlZ28gemFrcmVzdSBzxIUgb3puYWN6YW5lIGpha28gb2RzdGFqxIVjZS4gTmEgdGVqIHBvZHN0YXdpZSB3eWdlbmVyb3dhbm8gbWFjaWVyeiBsb2dpY3puxIUsIGdkemllIGthxbxkYSBrb23Ds3JrYSB3c2thenVqZSwgY3p5IGRhbmEgd2FydG/Fm8SHIHcgemJpb3J6ZSBkYW55Y2ggamVzdCBvZHN0YWrEhWNhLg0KDQpEbGEga2HFvGRlaiBvYnNlcndhY2ppIHcgemJpb3J6ZSBkYW55Y2ggcG9saWN6b25vIG5hc3TEmXBuaWUgbGljemLEmSBvZHN0YWrEhWN5Y2ggd2FydG/Fm2NpLiBPYnNlcndhY2plIHphd2llcmFqxIVjZSBjbyBuYWptbmllaiBkd2llIHdhcnRvxZtjaSBvZHN0YWrEhWNlICh6Z29kbmllIHogdXN0YWxvbnltIHByb2dpZW0pIHpvc3RhxYJ5IG96bmFjem9uZSBqYWtvIHBvdGVuY2phbG5pZSBwcm9ibGVtYXR5Y3puZS4gVGFraWUgb2JzZXJ3YWNqZSB6b3N0YcWCeSB3eWtsdWN6b25lIHogZGFsc3plaiBhbmFsaXp5LCBjbyBwb3p3b2xpxYJvIG9jennFm2NpxIcgZGFuZSBpIHptaW5pbWFsaXpvd2HEhyBpY2ggd3DFgnl3IG5hIG1vZGVsLg0KWm9zdGHFgm8gdXN1bmnEmXR5Y2ggYHIgbnJvdyhiYXR0ZXJ5X3RyYWluX2RhdGFbb3V0bGllcl9jb3VudCA+PSBvdXRsaWVyX3RocmVzaG9sZCxdKWAgcmVrb3Jkw7N3Lg0KDQpgYGB7cn0NCmRhdGFfc2NhbGVkIDwtIGFzLmRhdGEuZnJhbWUobGFwcGx5KGRhdGFfY2xlYW5lZCwgZnVuY3Rpb24oY29sKSB7DQogIGlmIChpcy5udW1lcmljKGNvbCkpIHsNCiAgICByZXNjYWxlKGNvbCkNCiAgfSBlbHNlIHsNCiAgICBjb2wgIA0KICB9DQp9KSkNCg0Kc2tpbShkYXRhX3NjYWxlZCkNCmBgYA0KDQpOYXN0xJlwbmllIGRhbmUgem9zdGHFgnkgem5vcm1hbGl6b3dhbmUsIGRvIGN6ZWdvIHd5a29yenlzdGFubyBmdW5rY2rEmSAqcmVzY2FsZSouDQoNCkRhbmUgem9zdGHFgnkgcG9kemllbG9uZSBuYSB6YmlvcnkgdHJlbmluZ293eSAoNzAlKSBpIHRlc3Rvd3kgKDMwJSkuIFBvZGN6YXMgdHJlbm93YW5pYSBtb2RlbHUgemFzdG9zb3dhbm8gbWV0b2TEmSB3YWxpZGFjamkga3J6ecW8b3dlaiwgYWJ5IHphcGV3bmnEhyBqZWdvIHN0YWJpbG5vxZvEhyBpIHVvZ8OzbG5pb27EhSBqYWtvxZvEhy4gRG9kYXRrb3dvLCBhYnkgdXBld25pxIcgc2nEmSwgxbxlIHJvemvFgmFkeSB6bWllbm5laiBjZWx1IHPEhSBwb2RvYm5lIHcgb2J1IHpiaW9yYWNoLCB6d2l6dWFsaXpvd2FubyBqZSBuYSBwb25pxbxzenltIHd5a3Jlc2llIGfEmXN0b8WbY2kuDQoNCmBgYHtyfQ0KDQppZHggPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihkYXRhX3NjYWxlZCRWb2x1bWV0cmljLkVuZXJneSwgcD0wLjcsIGxpc3Q9RikNCg0KdHJhaW5fZGF0YSA8LSBkYXRhX3NjYWxlZFtpZHgsIF0NCnRlc3RfZGF0YSA8LSBkYXRhX3NjYWxlZFstaWR4LCBdDQoNCmN0cmwgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDEwKQ0KDQpnZ3Bsb3QobWFwcGluZyA9IGFlcyhhbHBoYSA9IDAuNCkpICsgDQogIGdlb21fZGVuc2l0eShhZXMoeCA9IFZvbHVtZXRyaWMuRW5lcmd5LCBmaWxsID0gInJlZCIpLCBkYXRhID0gdHJhaW5fZGF0YSkgKyANCiAgZ2VvbV9kZW5zaXR5KGFlcyh4ID0gVm9sdW1ldHJpYy5FbmVyZ3ksIGZpbGwgPSAiYmx1ZSIpLCBkYXRhID0gdGVzdF9kYXRhKSArIA0KICB0aGVtZV9taW5pbWFsKCkNCg0KYGBgDQoNCmBgYHtyfQ0KbW9kZWw8LSBsbSgNCiAgVm9sdW1ldHJpYy5FbmVyZ3l+LiwgDQogIGRhdGEgPSB0cmFpbl9kYXRhLCANCiAgdHJDb250cm9sID0gY3RybA0KKQ0KYGBgDQoNCg0KIyMjIE9jZW5hIG1vZGVsdQ0KDQpEbyBvY2VueSBtb2RlbHUgd3licmFubyBtZXRyeWtpIFItc3F1YXJlZCwgUi1zcXVhcmVkIGFkanVzdGVkIG9yYXogUk1TRS4NCk1vZGVsIG9zacSFZ2EgYmFyZHpvIHd5c29raWUgZG9wYXNvd2FuaWUsIHd5amHFm25pYWrEhWMgOTcsNTclIHptaWVubm/Fm2NpIHptaWVubmVqIHphbGXFvG5laiAoUi1zcXVhcmVkKS4gU2tvcnlnb3dhbmEgd2FydG/Fm8SHIChSLXNxdWFyZWQgYWRqdXN0ZWQpIHd5bm9zesSFY2EgOTQsNDYlIHV3emdsxJlkbmlhIGxpY3pixJkgcHJlZHlrdG9yw7N3LCBjbyB3c2thenVqZSBuYSB3eXNva8SFIGpha2/Fm8SHIG1vZGVsdSBwcnp5IG1pbmltYWxueW0gd3DFgnl3aWUgbmFkbWllcm5lZ28gZG9wYXNvd2FuaWEuIE5pc2thIHdhcnRvxZvEhyBSTVNFICgwLDE3ODgpIHBvdHdpZXJkemEsIMW8ZSDFm3JlZG5pIGLFgsSFZCBwcmVkeWtjamkgamVzdCBuaWV3aWVsa2kgdyBrb250ZWvFm2NpZSBza2FsaSB6bWllbm5laiB6YWxlxbxuZWosIGNvIMWbd2lhZGN6eSBvIGR1xbxlaiBkb2vFgmFkbm/Fm2NpIG1vZGVsdS4gTW9kZWwgamVzdCBkb2JyemUgZG9wYXNvd2FueSBpIHByZWN5enlqbnkuDQoNCmBgYHtyfQ0KbW9kZWxfc3VtbWFyeSA8LSBzdW1tYXJ5KG1vZGVsKSANCnJtc2VfdmFsdWUgPC0gcm1zZSh0ZXN0X2RhdGEkVm9sdW1ldHJpYy5FbmVyZ3ksIHByZWRpY3Rpb25zKQ0KcmVzdWx0cyA8LSBkYXRhLmZyYW1lKA0KICBNZXRyeWthID0gYygiUi1zcXVhcmVkIiwgIlItc3F1YXJlZCBhZGp1c3RlZCIsICJSTVNFIiksDQogIFdhcnRvxZvEhyA9IGMoDQogICAgbW9kZWxfc3VtbWFyeSRyLnNxdWFyZWQsDQogICAgbW9kZWxfc3VtbWFyeSRhZGouci5zcXVhcmVkLA0KICAgIHJvdW5kKHJtc2VfdmFsdWUsIDQpDQogICkNCikNCg0Ka2FibGUoDQogIHJlc3VsdHMsDQogIGNvbC5uYW1lcyA9IGMoIk1ldHJ5a2EiLCAiV2FydG/Fm8SHIiksDQogIGFsaWduID0gImMiLA0KICBjYXB0aW9uID0gIld5bmlraSBtb2RlbHUiDQopDQoNCmBgYA0KDQpgYGB7cn0NCg0KY29lZmZpY2llbnRzIDwtIHRpZHkobW9kZWwpIA0KDQpyZXNpZHVhbHMgPC0gcmVzaWQobW9kZWwpDQpmaXR0ZWQgPC0gZml0dGVkKG1vZGVsKQ0KDQpyZXNpZHVhbHNfZml0dGVkX3Bsb3QgPC0gZ2dwbG90KGRhdGEgPSBkYXRhLmZyYW1lKHJlc2lkdWFscywgZml0dGVkKSwgYWVzKHggPSBmaXR0ZWQsIHkgPSByZXNpZHVhbHMpKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjYsIGNvbG9yID0gImJsdWUiKSArICANCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAicmVkIikgKw0KICBsYWJzKHRpdGxlID0gIlJlc3p0eSBhIGRvcGFzb3dhbmUgd2FydG/Fm2NpIiwgeCA9ICJEb3Bhc293YW5lIHdhcnRvxZtjaSIsIHkgPSAiUmVzenR5IikgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KcXFfcGxvdCA8LSBnZ3Bsb3QoZGF0YSA9IGRhdGEuZnJhbWUocmVzaWR1YWxzKSwgYWVzKHNhbXBsZSA9IHJlc2lkdWFscykpICsNCiAgc3RhdF9xcShjb2xvciA9ICJibHVlIiwgYWxwaGEgPSAwLjYpICsgIA0KICBzdGF0X3FxX2xpbmUoY29sb3IgPSAicmVkIikgKyANCiAgbGFicyh0aXRsZSA9ICJXeWtyZXMgUS1RIGRsYSByZXN6dCIsIHggPSAiVGVvcmV0eWN6bmUga3dhbnR5bGUiLCB5ID0gIkt3YW50eWxlIHByw7NieSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCnJlc2lkdWFsc19oaXN0IDwtIGdncGxvdChkYXRhID0gZGF0YS5mcmFtZShyZXNpZHVhbHMpLCBhZXMoeCA9IHJlc2lkdWFscykpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHkgPSAuLmRlbnNpdHkuLiksIGJpbnMgPSAzMCwgZmlsbCA9ICJibHVlIiwgYWxwaGEgPSAwLjYpICsNCiAgZ2VvbV9kZW5zaXR5KGNvbG9yID0gInJlZCIsIHNpemUgPSAxKSArDQogIGxhYnModGl0bGUgPSAiSGlzdG9ncmFtIHJlc3p0IiwgeCA9ICJSZXN6dHkiLCB5ID0gIkfEmXN0b8WbxIciKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQpwcmVkaWN0aW9ucyA8LSBwcmVkaWN0KG1vZGVsLCBuZXdkYXRhID0gdHJhaW5fZGF0YSkNCm9ic2VydmVkIDwtIHRyYWluX2RhdGEkVm9sdW1ldHJpYy5FbmVyZ3kNCm9ic2VydmVkX3ByZWRpY3RlZF9wbG90IDwtIGdncGxvdChkYXRhID0gZGF0YS5mcmFtZShvYnNlcnZlZCwgcHJlZGljdGlvbnMpLCBhZXMoeCA9IG9ic2VydmVkLCB5ID0gcHJlZGljdGlvbnMpKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjYsIGNvbG9yID0gImJsdWUiKSArDQogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAicmVkIikgKw0KICBsYWJzKHRpdGxlID0gIlJ6ZWN6eXdpc3RlIGEgcHJ6ZXdpZHl3YW5lIHdhcnRvxZtjaSIsIHggPSAiUnplY3p5d2lzdGUiLCB5ID0gIlByemV3aWR5d2FuZSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCmdyaWQuYXJyYW5nZShyZXNpZHVhbHNfZml0dGVkX3Bsb3QsIHFxX3Bsb3QsIHJlc2lkdWFsc19oaXN0LCBvYnNlcnZlZF9wcmVkaWN0ZWRfcGxvdCwgbmNvbCA9IDIpDQoNCmBgYA0KUmVzenR5IHPEhSByw7N3bm9taWVybmllIHJvenByb3N6b25lIHdva8OzxYIgcG96aW9tZWogbGluaWksIGEgaWNoIHJvemvFgmFkIG5pZSB3eWthenVqZSDFvGFkbnljaCB3eXJhxbpueWNoIHd6b3Jjw7N3IGN6eSB0ZW5kZW5jamkuIFN1Z2VydWplIHRvLCDFvGUgemHFgm/FvGVuaWUgbGluaW93b8WbY2kgbW9kZWx1IGplc3Qgc3BlxYJuaW9uZSBpIMWbd2lhZGN6eSBvIHBvcHJhd25laiBzcGVjeWZpa2FjamkgbW9kZWx1LiBEb2RhdGtvd28sIHd5a3JlcyBRLVEgZGxhIHJlc3p0IHd5a2F6dWplLCBpxbwgcm96a8WCYWQgdGVuIGplc3QgemJsacW8b255IGRvIG5vcm1hbG5lZ28sIHBvdHdpZXJkemFqxIVjIHR5bSBzYW15bSBrb2xlam5lIHdhxbxuZSB6YcWCb8W8ZW5pZSByZWdyZXNqaS4gSGlzdG9ncmFtIHJlc3p0IHLDs3duaWXFvCBwb3R3aWVyZHphIHRlbiB3bmlvc2VrLCBwcmV6ZW50dWrEhWMgc3ltZXRyeWN6bnkga3N6dGHFgnQgdHlwb3d5IGRsYSByb3prxYJhZHUgbm9ybWFsbmVnby4NCktsdWN6b3d5bSBlbGVtZW50ZW0gb2NlbnkgamFrb8WbY2kgbW9kZWx1IGplc3Qgd3lrcmVzIHBvcsOzd251asSFY3kgd2FydG/Fm2NpIHJ6ZWN6eXdpc3RlIGkgcHJ6ZXdpZHl3YW5lLCBrdMOzcnkgcG9rYXp1amUsIMW8ZSBwcmVkeWtjamUgbW9kZWx1IMWbY2nFm2xlIG9kcG93aWFkYWrEhSByemVjenl3aXN0eW0gb2JzZXJ3YWNqb20gLSBwdW5rdHkgdWvFgmFkYWrEhSBzacSZIHd6ZMWCdcW8IGxpbmlpIG8gbmFjaHlsZW5pdSB6YmxpxbxvbnltIGRvIDEsIGNvIMWbd2lhZGN6eSBvIGRvYnJ5bSBkb3Bhc293YW5pdSBtb2RlbHUuIE5pZXpuYWN6bmUgb2RjaHlsZW5pYSBvZCBsaW5paSBpZGVhbG5laiBzxIUgbm9ybWFsbmUgaSB3eW5pa2FqxIUgeiB3eXN0xJlwb3dhbmlhIHJlc3p0LCBrdMOzcmUgbmllIHpvc3RhxYJ5IHcgcGXFgm5pIHd5dMWCdW1hY3pvbmUgcHJ6ZXogbW9kZWwuDQoNCmBgYHtyfQ0KY29lZmZpY2llbnRzIDwtIHN1bW1hcnkobW9kZWwpJGNvZWZmaWNpZW50cw0KY29lZmZpY2llbnRzX3NvcnRlZCA8LSBjb2VmZmljaWVudHNbb3JkZXIoYWJzKGNvZWZmaWNpZW50c1ssICJFc3RpbWF0ZSJdKSwgZGVjcmVhc2luZyA9IFRSVUUpLCBdDQoNCmRhdGF0YWJsZShjb2VmZmljaWVudHNfc29ydGVkLCANCiAgICAgICAgICBvcHRpb25zID0gbGlzdChwYWdlTGVuZ3RoID0gMTAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIGF1dG9XaWR0aCA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgc2VhcmNoSGlnaGxpZ2h0ID0gVFJVRSkpDQpgYGANCg0KDQpgYGB7cn0NCmltcG9ydGFuY2UgPC0gdmFySW1wKG1vZGVsKQ0KDQpzb3J0ZWRfYXR0cl9pbXBvcnRhbmNlIDwtIGltcG9ydGFuY2UgJT4lDQogIGFycmFuZ2UoZGVzYyhPdmVyYWxsKSkNCg0Kc29ydGVkX2F0dHJfaW1wb3J0YW5jZQ0KYGBgDQoNCg0KIyMgUHJlZHlrY2phIMWbcmVkbmllZ28gbmFwacSZY2lhIG1ldG9kxIUgcmFuZG9tIGZvcmVzdA0KDQpXIHJvemR6aWFsZSB6b3N0YW5pZSBwcnplYW5hbGl6b3dhbmEgcHJlZHlrY2phIMWbcmVkbmllZ28gbmFwacSZY2lhIChBdmVyYWdlIFZvbHRhZ2UpIHByenkgdcW8eWNpdSBtb2RlbHUgUmFuZG9tIEZvcmVzdC4gTW9kZWwgdGVuIHBvendhbGEgdWNod3ljacSHIG5pZWxpbmlvd2UgemFsZcW8bm/Fm2NpIGkgemlkZW50eWZpa293YcSHIGtsdWN6b3dlIGN6eW5uaWtpIHdwxYJ5d2FqxIVjZSBuYSBuYXBpxJljaWUuIFByemVkc3Rhd2lvbmUgem9zdGFuxIUgbWlhcnkgamFrb8WbY2kgb3JheiB6bmFjemVuaWUgem1pZW5ueWNoLg0KDQpgYGB7cn0NCmF0dHJpYnV0ZXNfdG9fcmVtb3ZlX3JmIDwtIGNvbG5hbWVzKHNlbGVjdChkYXRhLCBjKEJhdHRlcnkuSUQsIEJhdHRlcnkuRm9ybXVsYSwgRm9ybXVsYS5EaXNjaGFyZ2UsIEZvcm11bGEuQ2hhcmdlKSkpDQpgYGANCg0KVyBwaWVyd3N6eW0ga3Jva3UgdXN1bmnEmXRvIHd5YnJhbmUga29sdW1ueSB0YWtpZSBqYWsgYHIgYXR0cmlidXRlc190b19yZW1vdmVfcmZgLCBrdMOzcmUgbmllIHdub3NpxYJ5IGJlenBvxZtyZWRuaWNoIGluZm9ybWFjamkgZG8gbW9kZWxvd2FuaWEuDQoNCmBgYHtyfQ0KdHJhaW5fZGF0YV9yZiA8LSBzZWxlY3QoZGF0YSwgLShhdHRyaWJ1dGVzX3RvX3JlbW92ZV9yZikpDQpjb3JfdGhlcmVzaG9sZCA8LSAwLjgNCmNvcl93aXRoX3RhcmdldCA8LSBjb3IoZGF0YXNldF9udW1lcmljYWxfYXR0cnMsIHVzZSA9ICJwYWlyd2lzZS5jb21wbGV0ZS5vYnMiKVssICJBdmVyYWdlLlZvbHRhZ2UiXQ0KaGlnaF9jb3JfdG9fdGFyZ2V0IDwtIG5hbWVzKHdoaWNoKGNvcl93aXRoX3RhcmdldCA+IDAuOCkpDQp0cmFpbl9kYXRhX3JmIDwtIHRyYWluX2RhdGFfcmYgICU+JQ0KICBzZWxlY3QoLWFsbF9vZihoaWdoX2Nvcl90b190YXJnZXQpLCBBdmVyYWdlLlZvbHRhZ2UpDQoNCmBgYA0KDQpOYXN0xJlwbmllLCBvYmxpY3pvbm8ga29yZWxhY2rEmSB6bWllbm55Y2ggbnVtZXJ5Y3pueWNoIHplIHptaWVubsSFIGNlbHUsIHppZGVudHlmaWtvd2FubyBpIHVzdW5pxJl0byB6bWllbm5lIG8gd3lzb2tpZWoga29yZWxhY2ppIGxpbmlvd2VqIChwcnpla3JhY3phasSFY2VqIHdhcnRvxZvEhyBgciBjb3JfdGhlcmVzaG9sZGApLiBBdHJ5YnV0eSwga3TDs3JlIHpvc3RhxYJ5IHVzdW5pxJl0ZSB0byBgciBoaWdoX2Nvcl90b190YXJnZXRgLg0KDQpgYGB7cn0NCg0Kb3V0bGllcl90aHJlc2hvbGQgPC0gMw0KDQpvdXRsaWVyX21hdHJpeCA8LSBhcy5kYXRhLmZyYW1lKGxhcHBseSh0cmFpbl9kYXRhX3JmLCBmdW5jdGlvbihjb2x1bW4pIHsNCiAgaWYgKGlzLm51bWVyaWMoY29sdW1uKSkgew0KICAgIHJldHVybihkZXRlY3Rfb3V0bGllcnMoY29sdW1uKSkNCiAgfSBlbHNlIHsNCiAgICByZXR1cm4ocmVwKEZBTFNFLCBsZW5ndGgoY29sdW1uKSkpDQogIH0NCn0pKQ0KDQpvdXRsaWVyX2NvdW50IDwtIHJvd1N1bXMob3V0bGllcl9tYXRyaXgpDQpyZW1vdmVkX2F0dHJzIDwtIG5yb3codHJhaW5fZGF0YV9yZltvdXRsaWVyX2NvdW50ID49IG91dGxpZXJfdGhyZXNob2xkLF0pDQoNCmRhdGFfY2xlYW5lZF9yZiA8LSB0cmFpbl9kYXRhX3JmW291dGxpZXJfY291bnQgPCBvdXRsaWVyX3RocmVzaG9sZCwgXQ0Kc2tpbShkYXRhX2NsZWFuZWRfcmYpDQpgYGANCg0KWmUgemJpb3J1IHVzdW5pxJl0byBgciByZW1vdmVkX2F0dHJzYCByZWtvcmTDs3cuDQoNCmBgYHtyfQ0KZGF0YV9yZiA8LSB0cmFpbl9kYXRhX3JmDQpoZWFkKGRhdGFfcmYpDQppZHhfcmYgPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihkYXRhX3JmJEF2ZXJhZ2UuVm9sdGFnZSwgcD0wLjcsIGxpc3Q9RikNCg0KdHJhaW5fZGF0YV9yZiA8LSBkYXRhX3JmW2lkeCwgXQ0KdGVzdF9kYXRhX3JmIDwtIGRhdGFfcmZbLWlkeCwgXQ0KDQpjdHJsIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAxMCkNCg0KZ2dwbG90KG1hcHBpbmcgPSBhZXMoYWxwaGEgPSAwLjQpKSArIA0KICBnZW9tX2RlbnNpdHkoYWVzKHggPSBBdmVyYWdlLlZvbHRhZ2UsIGZpbGwgPSAicmVkIiksIGRhdGEgPSB0cmFpbl9kYXRhX3JmKSArIA0KICBnZW9tX2RlbnNpdHkoYWVzKHggPSBBdmVyYWdlLlZvbHRhZ2UsIGZpbGwgPSAiYmx1ZSIpLCBkYXRhID0gdGVzdF9kYXRhX3JmKSArIA0KICB0aGVtZV9taW5pbWFsKCkNCg0KYGBgDQoNClpiaW9yeSBtYWrEhSB6YmxpxbxvbmUgcm96a8WCYWR5IHptaWVubmVqIGNlbHUuDQoNCmBgYHtyfQ0KDQptb2RlbF9yZiA8LSB0cmFpbigNCiAgQXZlcmFnZS5Wb2x0YWdlIH4uLCANCiAgZGF0YSA9IHRyYWluX2RhdGFfcmYsIA0KICBtZXRob2QgPSAicmYiLCANCiAgbWV0cmljID0gIlJNU0UiLA0KICB0ckNvbnRyb2wgPSBjdHJsLCANCiAgdHVuZUdyaWQgPSBleHBhbmQuZ3JpZChtdHJ5PTMpDQogICkNCg0KcHJlZGljdGlvbnMgPC0gcHJlZGljdChtb2RlbF9yZiwgbmV3ZGF0YSA9IHRlc3RfZGF0YV9yZikNCg0KYGBgDQojIyMgT2NlbmEgbW9kZWx1DQoNClcgY2VsdSBvY2VueSBqYWtvxZtjaSBtb2RlbGkgd3lrb3J6eXN0YW5vIG1ldHJ5a2kgUk1TRSwgUi1zcXVhcmVkIGkgTUFFLCBwb25pZXdhxbwgdW1vxbxsaXdpYWrEhSBvbmUga29tcGxla3Nvd8SFIGFuYWxpesSZIGLFgsSZZMOzdyBwcmVkeWtjamkgb3JheiBkb3Bhc293YW5pYSBtb2RlbHUgZG8gZGFueWNoLg0KDQpgYGB7cn0NCg0KZ2dwbG90KGRhdGEuZnJhbWUoQWN0dWFsID0gdGVzdF9kYXRhX3JmJEF2ZXJhZ2UuVm9sdGFnZSwgUHJlZGljdGVkID0gcHJlZGljdGlvbnMpLCBhZXMoeCA9IEFjdHVhbCwgeSA9IFByZWRpY3RlZCkpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNiwgY29sb3IgPSAiYmx1ZSIpICsNCiAgZ2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiKSArDQogIGxhYnModGl0bGUgPSAiV2FydG/Fm2NpIHJ6ZWN6eXdpc3RlIGEgcHJlZHlrY2phIiwgeCA9ICJSemVjenl3aXN0ZSIsIHkgPSAiUHJ6ZXdpZHl3YW5lIikNCg0KcmVzdWx0cyA8LSBkYXRhLmZyYW1lKA0KICBNZXRyeWthID0gYygiUk1TRSIsICJSLXNxdWFyZWQiLCAiTUFFIiksDQogIFdhcnRvxZvEhyA9IGMoDQogICAgbW9kZWxfcmYkcmVzdWx0cyRSTVNFLCAgICAgDQogICAgbW9kZWxfcmYkcmVzdWx0cyRSc3F1YXJlZCwNCiAgICBtb2RlbF9yZiRyZXN1bHRzJE1BRQ0KICApDQopDQoNCmthYmxlKA0KICByZXN1bHRzLA0KICBjb2wubmFtZXMgPSBjKCJNZXRyeWthIiwgIldhcnRvxZvEhyIpLA0KICBhbGlnbiA9ICJjIiwNCiAgY2FwdGlvbiA9ICJXeW5pa2kgbW9kZWx1IFJhbmRvbSBGb3Jlc3QiDQopDQpgYGANCg0KUk1TRSB3c2thenVqZSwgxbxlIHByemVjacSZdG55IGLFgsSFZCBwcmVkeWtjamkgd3lub3NpIG9rb8WCbyAwLjUzLiBBdHJ5YnV0IHByenlqbXVqZSB3YXJ0b8WbY2kgeiBwcnplZHppYcWCdSAoLTc7NS41KSwgYSB3acSZYyBSTVNFIGplc3Qgc3Rvc3Vua293byBuaXNraS4gU3VnZXJ1amUgdG8sIMW8ZSBtb2RlbCBkb2JyemUgcHJ6ZXdpZHVqZSB3YXJ0b8WbxIcgbmFwacSZY2lhIHcgd2nEmWtzem/Fm2NpIHByenlwYWRrw7N3LCBwcnp5IHVtaWFya293YW55bSBixYLEmWR6aWUuDQpXYXJ0b8WbxIcgJFJeMiA9IDAuODk0JCBvem5hY3phLCDFvGUgbW9kZWwgd3lqYcWbbmlhIDg5LjQlIHptaWVubm/Fm2NpIHptaWVubmVqIHphbGXFvG5lai4gSmVzdCB0byBiYXJkem8gZG9icnkgd3luaWssIHdza2F6dWrEhWN5LCDFvGUgbW9kZWwgZG9icnplIGRvcGFzb3d1amUgc2nEmSBkbyBkYW55Y2ggaSB3acSZa3N6b8WbxIcgb2JzZXJ3YWNqaSBtb8W8ZSBiecSHIHByemV3aWR6aWFuYSBuYSBwb2RzdGF3aWUgZG9zdMSZcG55Y2ggY2VjaC4NCg0KYGBge3J9DQoNCmltcG9ydGFuY2VfZGYgPC0gdmFyaWFibGVfaW1wb3J0YW5jZSRpbXBvcnRhbmNlICU+JQ0KICBhcy5kYXRhLmZyYW1lKCkgJT4lDQogIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKHZhciA9ICJWYXJpYWJsZSIpICU+JQ0KICBhcnJhbmdlKGRlc2MoT3ZlcmFsbCkpDQoNCmdncGxvdChpbXBvcnRhbmNlX2RmLCBhZXMoeCA9IHJlb3JkZXIoVmFyaWFibGUsIE92ZXJhbGwpLCB5ID0gT3ZlcmFsbCkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAic3RlZWxibHVlIiwgYWxwaGEgPSAwLjgpICsNCiAgY29vcmRfZmxpcCgpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJXYcW8bm/Fm8SHIHptaWVubnljaCAoUmFuZG9tIEZvcmVzdCkiLA0KICAgIHggPSAiWm1pZW5uYSIsDQogICAgeSA9ICJabmFjemVuaWUgKE92ZXJhbGwpIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUoDQogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgc2l6ZSA9IDE2LCBmYWNlID0gImJvbGQiKSwNCiAgICBheGlzLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNCksDQogICAgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMikNCiAgKQ0KDQpgYGANCk5hamJhcmR6aWVqIGlzdG90bmUgem1pZW5uZSBkbGEgcHJlZHlrY2ppIG1vZGVsdSB0byBlbmVyZ2lhIGdyYXdpbWV0cnljem5hIGkgd29sdW1ldHJ5Y3puYS4gDQoNCiMgxblyw7NkxYJh